This tutorial provides a comprehensive guide to understanding and working with the Domain Specific Language (NammaDSL) in the NammaYatri. It covers the creation of YAML files, code generation, and compilation, as well as the syntax for defining APIs and storage.
Location: Create a YAML file inside the spec
folder of the module you are working on. For instance, if you are working on the rider-app
, the path would be:
rider-platform/rider-app/spec
Depending on the type of specification you are defining, you may place the file inside either the API
or Storage
folder.
Code Generation: After defining the YAML files, execute the following command to generate the code:
, run-generator
This command generates Haskell Beam, query, and domain files in the src-read-only
directory, as well as the SQL queries.
Important Note: This command will only generate those spec files which are new or changed. This is done by getting the current hash of the spec file and comparing with the file hash of the HEAD commit
, run-generator --all
use "--all" args to generate all the spec files
Compilation: Compile the code using:
cabal build all
imports
: Used for importing predefined types.
importPackageOverrides
: Used to override import packages See More
module
: Specifies the name of the module.
Note: In case of dashboard APIs, all APIs in the module have the same API prefix, by default it is module
converted to camel case.
apiPrefix
: overwrite default API prefix for main and helper dashboard API (optional, specific for dashboard).
Note: Empty value ""
allowed for apiPrefix
andhelperApiPrefix
, it means no API prefix.
helperApiPrefix
: overwrite API prefix for helper dashboard API (optional, specific for dashboard).
types
: Defines the request and response types for your APIs. This field is optional. Same as Complex Types in Storage DSL. Types are defined in the following format:
{TypeName}:
- {field1}: {field1Type}
- {field2}: {field2Type}
- derive: {Any extra derivation}
Note: Old syntax does not preserve fields order and will be deprecated.
{TypeName}:
{field1}: {field1Type}
{field2}: {field2Type}
derive: {Any extra derivation}
Enum types can be defined as:
{TypeName}:
- enum: {enum1},{enum2}
- derive: {Any extra derivation}
To make newtype or type instead of data use recordType: NewType | Data (Default) | Type
{TypeName}:
- recordType: NewType
- {fieldName}: {fieldType}
- derive: {Any extra derivation}
{TypeName}:
- recordType: Type
- type: {fieldType}
To create default HideSecrets instance use derive
keyword (specific for dashboard)
{TypeName}:
- {fieldName}: {fieldType}
- derive: "'HideSecrets"
apis
: Contains all the APIs in the following format:
{httpMethod}
(HTTP method GET | POST | PUT | DELETE)
endpoint
: API path
/path1/path2/{pathParam1}/path3/{pathParam2}
Note: Path param types should be mentioned in the params part below.
name
: API name. Sometimes two different APIs have the same API name auto generated from path, so it can be overwritten (optional)
Note: Be careful when you change apiName
for already existing API. When apName
changed, Endpoint
and UserActonType
generation will be also changed, old data should be migrated as follow:
migrate:
endpoint: <oldEndpoint>
userActionType: <oldUserActionType>
Typically,<oldEndpoint>
and <oldUserActionType>
are the same values in this format:
PROVIDER_MANAGEMENT/BOOKING/POST_BOOKING_CANCEL_ALL_STUCK
response
:
type
: Type of responserequest
:
type
: Type of request (optional)multipart
:
type
: Type of request in case of multipart request (optional)auth
: Authentication method (default: TokenAuth) See More
query
: List of query parameters
- {queryParam1}: {queryParam1Type}
Note: Old syntax does not preserve params order and will be deprecated.
{queryParam1}: {queryParam1Type}
mandatoryQuery
: List of mandatory query parameters
- {mandatoryQueryParam1}: {mandatoryQueryParam1Type}
Note: Old syntax does not preserve params order and will be deprecated.
{mandatoryQueryParam1}: {mandatoryQueryParam1Type}
params
: List of path parameters
{pathParam1}: {pathParam1Type}
{pathParam2}: {pathParam2Type}
headers
: List of headers
headers:
- {header1}: {headerType1}
- {header2}: {headerType2}
helperApi
: Recursively contains dashboard helper API in the same format as main API (optional, specific for dashboard)
validation
: Qualified name for request validation function (optional)
Example:
imports: {}
module: Sos
types:
SosRes:
- sosId: Id Sos
SosDetailsRes:
- sos: Maybe Sos
SosReq:
- flow: SosType
- rideId: Id Ride
SosUpdateReq:
- status: SosStatus
- comment: Maybe Text
apis:
# GET /sos/getDetails
- GET:
endpoint: /sos/getDetails/{rideId}
auth: TokenAuth
params:
rideId: Id Ride
response:
type: API.Types.UI.Sos.SosDetailsRes
# # POST /sos/{sosId}/status
- POST:
endpoint: /sos/{sosId}/status
auth: TokenAuth RIDER_TYPE
params:
sosId: Id Sos
request:
type: API.Types.UI.Sos.SosUpdateReq
response:
type: Kernel.Types.APISuccess.APISuccess
auth: ApiAuth DRIVER_OFFER_BPP_MANAGEMENT DRIVERS LIST
imports
: Used for importing predefined types. See More{dataTypeName}
: Specifies the name of the module.
tableName
: Optional name of the table, It takes the snake_case of the dataTypeName
if not defined.
fields
: Lists all fields of the table with Haskell type. See More
constraints
: PrimaryKey | SecondaryKey | NotNull | AUTOINCREMENT
importPackageOverrides
: Used to override import packages See More
types
: User-defined types, similar to API types. See More
derives
: Override derives of the main Data type.
derives: "Show,Eq,Ord"
beamType
: User-defined beam type for a specified data type. See More
beamFields
: User-defined beam field name change or use it if you want to have something different on beamside. See More
beamInstance
: We can mention the beam instance we need using this field See More
sqlType
: User-defined sql type for a field. See More
default
: Default sql value for fields, if any
fields:
scheduleTryTimes: '[Int]'
tripCategory: Text
default:
tripCategory: "'All'"
scheduleTryTimes: "'{1800, 900, 300}'"
queries
: All Beam queries for the table. See More
cachedQueries:
Cached queries for the table. See More
fromTType
: FromTType of fields, if applicable. See More
toTType
: ToTType of fields, if applicable. See More
excludedFields
: There are some common fields like merchantId, merchantOperatingCityId, createdAt and updatedAt which are auto added in the data type. To remove them use this.
excludedFields:
- merchantOperatingCityId
- merchantId
extraIndexes
: Any additional indexes
See More
extraOperations
: Extra Operations See More
You have to provide the module name in imports
imports:
Merchant: Domain.Types.Merchant
FRFSSearch: Domain.Types.FRFSSearch
Note: These common types are auto imported, so you can directly use them without importing
Text -> Kernel.Prelude
Maybe -> Kernel.Prelude
Double -> Kernel.Prelude
TimeOfDay -> Kernel.Prelude
Day -> Data.Time.Calendar
Int -> Kernel.Prelude
Bool -> Kernel.Prelude
Id -> Kernel.Types.Id
ShortId -> Kernel.Types.Id
UTCTime -> Kernel.Prelude
Meters -> Kernel.Types.Common
HighPrecMeters -> Kernel.Types.Common
Kilometers -> Kernel.Types.Common
HighPrecMoney -> Kernel.Types.Common
Seconds -> Kernel.Types.Common
imports:
DataType1: Domain.Types.DataType1
To change package:
importPackageOverrides:
Domain.Types.DataType1: dashboard-api
Generated import in haskell:
import "dashboard-api" Domain.Types.DataType1
Sometimes we may need to skip package override when we generate in the same package. Then we should specify package mapping in dhall
configs:
, _packageMapping =
[ { _1 = GeneratorType.API_TYPES, _2 = "dashboard-api" }
{ _1 = GeneratorType.SERVANT_API, _2 = "rider-app" }
]
Generated import in haskell for API_TYPES
:
import "this" Domain.Types.DataType1
Generated import in haskell for SERVANT_API
:
import "dashboard-api" Domain.Types.DataType1
In the field section mention field name and it's corresponding Haskell type
imports : {}
LmsModule:
tableName : lms_module
fields:
id : Id LmsModule
merchantOperatingCityId : Id MerchantOperatingCity
category : LmsCategory
createdAt : UTCTime
updatedAt : UTCTime
duration : Int
noOfVideos : Int
rank : Int
variant : Maybe Variant
moduleCompletionCriteria : ModuleCompletionCriteria
For Simple field types (which are just imported) it will be copied in the beam side as well unless we mention a specific Beam Type
If the Field is a Complex Type then it would be recursively split in the beam side unless we mention a Specific Beam Type
In case of Id, ShortId the Beam Type will be considered as Text
If we want Imported Data type on domain side and corresponding Id on the beam side you we use the WithId extensions with the type definition.
fields:
field1: Int
beamType:
field1: Text
fields:
a: Int
b: Text
beamFields:
a: "aa"
b: "bb"
# SomeType = SomeType {
# integerValueInText :: Text,
# version :: Int
# }
import:
SomeType: Domain.Types.SomeType
Some:
fields:
id: Id Some
val: SomeType # See this is an imported type
beamFields:
val:
intValue: Int
intValueInText: Text
# We have to right the toTType and fromTType functions
toTType:
intValue: (Kernel.Prelude.read . Domain.Types.SomeType.integerValueInText)
intValueInText: Domain.Types.SomeType.integerValueInText
fromTType:
val: mkVal
data Some = Some
{ id :: Kernel.Types.Id.Id Domain.Types.Some.Some,
val :: Domain.Types.SomeType.SomeType,
merchantId :: Kernel.Prelude.Maybe (Kernel.Types.Id.Id Domain.Types.Merchant.Merchant),
merchantOperatingCityId :: Kernel.Prelude.Maybe (Kernel.Types.Id.Id Domain.Types.MerchantOperatingCity.MerchantOperatingCity),
createdAt :: Kernel.Prelude.UTCTime,
updatedAt :: Kernel.Prelude.UTCTime
}
deriving (Generic, Show, ToJSON, FromJSON, ToSchema)
data SomeT f = SomeT
{ id :: B.C f Kernel.Prelude.Text,
intValue :: B.C f Kernel.Prelude.Int,
intValueInText :: B.C f Kernel.Prelude.Text,
merchantId :: B.C f (Kernel.Prelude.Maybe (Kernel.Prelude.Text)),
merchantOperatingCityId :: B.C f (Kernel.Prelude.Maybe (Kernel.Prelude.Text)),
createdAt :: B.C f Kernel.Prelude.UTCTime,
updatedAt :: B.C f Kernel.Prelude.UTCTime
}
deriving (Generic, B.Beamable)
beamInstance: MakeTableInstances
$(mkTableInstances ''PersonT "person")
beamInstance: MakeTableInstancesGenericSchema
$(mkTableInstancesGenericSchema ''PersonT "person")
beamInstance: MakeTableInstancesWithTModifier [("deviceOS", "device_o_s")]
$(mkTableInstancesWithTModifier ''MetaDataT "meta_data" [("deviceOS", "device_o_s")])
beamInstance: Custom mkCacParseInstace [[Table2],[Table3]]
$(mkCacParseInstace ''MetaDataT [[Table2], [Table3]])
beamInstance: Custom mkCacParseInstace "table_name" [Table2] [Table3] [(a,b,c)]
beamInstance:
- MakeTableInstances
- Custom mkCacParseInstace [[Table2],[Table3]]
- Custom Tool.Something.mkSomething "abc" [(a,b,c)] [[a],[b],[c]]
$(mkTableInstances ''PersonT "person")
$(mkCacParseInstace ''MetaDataT [[Table2], [Table3]])
$(Tool.Something.mkSomething ''MetaDataT "abc" [(a,b,c)] [[a],[b],[c]])
fields:
field1: "[Int]"
sqlType:
field1: "text[]"
toTType:
subscriberUrl: Kernel.Prelude.func1|I
gatewayUrl: showBaseUrlSimple # This function will be created in seperated file as it's not imported
registryUrl: showBaseUrl|M # This function will be created in seperated file as it's not imported
fromTType:
updatedAt: Kernel.Prelude.fromMaybe createdAt|I
isScheduled: makeSchelude
tripCategory: Kernel.Prelude.fromMaybe (Domain.Types.Common.OneWay Domain.Types.Common.OneWayOnDemandDynamicOffer)|I
fields:
isVerified: Bool
verificationUrl: Text
aadharId: Text
toTType:
isVerified: (K.B.verify aadharId verificationUrl)|E
toTType:
isVerified: K.B.verify isVerified aadharId verificationUrl)|EM
toTType:
isVerified: K.B.verify isVerified (K.B.C.isCorrectAadhar aadharId) verificationUrl)|EM
WithId: This is used when we want's imported data type's Id as the Beam Field. This does not create the data type in the create beam query.
WithCachedId: Same as WithId, only difference is the create and findbyId query is imported from Storage.CachedQuery.*
WithIdCreate, WithCachedIdCreate: Use this when its required to create the data type in create query. Important: The imported Data type should have create function in it's corresponding query file Example:
fields:
fareParams: Maybe FareParameters|WithIdCreate
farePolicy: Maybe FarePolicy|WithCachedId
Generated create query:
create :: (EsqDBFlow m r, MonadFlow m, CacheFlow m r) => Domain.Types.Estimate.Estimate -> m ()
create tbl = do
Kernel.Prelude.whenJust tbl.fareParams Storage.Queries.FareParameters.create
createWithKV tbl
Generated ToTType and FromTType conversions:
instance FromTType' Beam.Estimate Domain.Types.Estimate.Estimate where
fromTType' Beam.EstimateT {..} = do
fareParams' <- maybe (pure Nothing) (Storage.Queries.FareParameters.findById . Kernel.Types.Id.Id) fareParamsId
farePolicy' <- maybe (pure Nothing) (Storage.CachedQueries.FarePolicy.findById . Kernel.Types.Id.Id) farePolicyId
pure $
Just
Domain.Types.Estimate.Estimate
{
fareParams = fareParams',
farePolicy = farePolicy'
}
instance ToTType' Beam.Estimate Domain.Types.Estimate.Estimate where
toTType' Domain.Types.Estimate.Estimate {..} = do
Beam.EstimateT
{ Beam.fareParamsId = (Kernel.Types.Id.getId . (.id) <$>) fareParams,
Beam.farePolicyId = (Kernel.Types.Id.getId . (.id) <$>) farePolicy
}
Syntax:
queries:
{query function name}:
kvFunction: {kv function name}
params: [field1, field2 .. ] Array of field to be updated in update queries
where:
{where clause}
orderby: {field name} (optional)
Where Clause syntax:
where:
- {operator1}:
- field1
- {operator2}:
- field2
- field3
- {operator3}:
- field4
- field5
Fields that are used in params and where clause can be of 3 types:
where:
not_eq
where:
not_eq:
- id: id2|B
myname|CS -> "myname"
123|CI -> 123
123|CS -> "123"
0.23|CD -> 0.23
"0.34"|CS -> "0.23"
true|CB -> True
Domain.Something.defaultValue|CIM -> Domain.Something.defaultValue (and Domain.Something is added to imports)
kvFunction: updateWithKV
params:
- status: newStatus
where:
eq:
- status: NEW|CIM
kvFunction: updateWithKV
params:
- status: Domain.Types.DataType.CONFIRMED|CIM
where:
eq:
- status: NEW|CIM
where:
and:
- eq:
- status: NEW|CIM
- id|B
List of where operators:
Example:
LmsModule:
fields:
id: Id LmsModule
category: LmsCategory
question: Question
field1: Text
field2: Text
field3: Text
types:
LmsCategory:
enum: "Safety, Financial, Training"
QuestionType:
enum: "Maths, Physics, Chemistry"
derive: "Show"
Question:
question: Text
tp: QuestionType
derive: "Show,Eq"
queries:
findByComplexCondition:
kvFunction: findAllWithOptionsKV
where:
and:
- field1
- or:
- field2
- field3
- category
- question
orderBy: createdAt
updateQuestionById:
kvFunction: updateWithKV
params:
- question
where:
id
Generated Query:
findAllWithKV
[ Se.And
[ Se.Is Beam.field1 $ Se.Eq field1,
Se.Or
[ Se.Is Beam.field2 $ Se.Eq field2,
Se.Is Beam.field3 $ Se.Eq field3
],
Se.Is Beam.category $ Se.Eq category,
Se.Is Beam.questionQuestion $ Se.Eq $ Domain.Types.LmsModule.question question,
Se.Is Beam.questionTp $ Se.Eq $ Domain.Types.LmsModule.tp question
]
]
updateQuestionById question (Kernel.Types.Id.Id id) = do
_now <- getCurrentTime
updateWithKV
[ Se.Set Beam.questionQuestion $ Domain.Types.LmsModule.question question,
Se.Set Beam.questionTp $ Domain.Types.LmsModule.tp question,
Se.Set Beam.updatedAt _now
]
[ Se.Is Beam.id $ Se.Eq id
]