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

Brain dump of demos #7

Open
martypitt opened this issue Oct 8, 2024 · 2 comments
Open

Brain dump of demos #7

martypitt opened this issue Oct 8, 2024 · 2 comments

Comments

@martypitt
Copy link
Contributor

Add comments to this issue to capture demos that we need to build. We'll review, then split them out into seperate issues

@martypitt
Copy link
Contributor Author

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.

One's we've built on-the-fly:

can use a variable from a saved query in a field projection

      query FindSomeFilms( starring : PersonName ) {
         find { Film[] } as  {
            title: Title
            starring: filter(Actor[], (PersonName) -> PersonName == starring) as {
              name : PersonName
           }[]
         }[]
      }

Filtering a stream using .filterEach()

Defining / refining scope in a projection:

assigning a name to the scope:

See can project using a named scope at the top level

find { Film[] } as (film:Film) -> {
   movieName : film.title
}[]

Refining to a property:

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
}[]

Refining using a function

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
         }

not sure what to call this:

         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
               }
            }[]

Projecting an inline array:

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
            }
         }

Scoped params on tests:

Passing a filmId as input to query

query FindFilm( filmId : FilmId ) {
   find { Film( FilmId == filmId ) }
}

Casting

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 }
         }

Casting in a given clause:

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 }

Using a query param in a function

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)

Collections

Can use properties of collections to discover other collections:

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" }
    ]
  }
]

Using ids in collections

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\" }"
      }
   ]
}
}

Joining streams

Joining streams using |

      service TweetService {
         operation tweets():Stream<Tweet>
         operation analytics():Stream<TweetAnalytics>
         operation getUser(UserId):User
      }

stream { Tweet | TweetAnalytics }
  as {
     id : MessageId
     body : Message
     views : ViewCount?
}[]

Mutations

Output of a stream / query is transformed to the input of a mutation function

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()

Parameter and Closed

  • closed model can not be constructed - only returned from a service
  • parameter model should be constructed when passing to a service
  • a closed 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[]
      }

Converting things

Converting using as

converting using 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)
         }

Spread operator

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}
            }

Projection shorthand

find { Person } as {
   name, // reads name from Person
   address // etc
}

Restricting which services get called:

These examples from TaxiQlServiceRestrictionsSpec:

Only calling services with using:

         closed model Film {
            title : Title inherits String
         }
         service FilmService {
            operation getFilms():Film[]
            operation getBlockbusters():Film[]
         }

         find { Film[] }
         using { FilmService::getFilms }

Mixing services and operations:

            service NetflixService {
               operation getFilms():Film[]
               operation getBlockbusters():Film[]
            }

            find { Film[] }
            using { FilmService::getBlockbusters , FilmService::getFilms, NetflixService }

Excluding a service:

            find { Film[] }
            excluding { FilmService::films }

@andrewgkew
Copy link
Collaborator

Examples from Metro Bank implementation

1. Complex Enums

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" }] })
}

2. Access enum fields for enrichment

Using the custom errors above

  Errors.enumFromName('CODE1').code
  Errors.enumFromName('CODE1').message
  Errors.enumFromName('CODE1').error

3. Type that is an expression

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
}

4. Custom Errors (Exceptions)

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
})

5. Other enum methods

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
}

6. Conditional statement in a type

import taxi.stdlib.lowerCase

type AccountType inherits String by when(lowerCase(CustomerType)) {
  'type1' -> 'Personal'
  'type2' -> 'Personal'
  else -> 'Business'
}

7. Default a type to static string

type SchemeName inherits String by "STATIC.STRING"

8. API Key using on a service

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
  }
}

9. Parameterize the url for an API being called

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}
  }
}

11. Extension functions

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()))

12. Using functions in a complex type

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()

13. Using a projection in a query to set context

This example may need some more info

given {
  accountId
}
find {
  account(id == accountId)
} as (
  consent: Consent = Response:Consent[].first(),
  account: Account
) -> {
  data: AccountResponse
}

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

No branches or pull requests

2 participants