
Purpose: Framework Core Low-Level Interface/SOLID Processing
- this unit is a part of the Open Source Synopse mORMot framework 2, licensed under a MPL/GPL/LGPL three license - see LICENSE.md
| Unit Name | Description | |
|---|---|---|
| mormot.core.base | Framework Core Shared Types and RTL-like Functions | |
| mormot.core.buffers | Framework Core Low-Level Memory Buffer Process | |
| mormot.core.data | Framework Core Low-Level Data Processing Functions | |
| mormot.core.json | Framework Core Low-Level JSON Processing | |
| mormot.core.log | Framework Core Logging | |
| mormot.core.os | Framework Core Low-Level Wrappers to the Operating-System API | |
| mormot.core.rtti | Framework Core Low-Level Cross-Compiler RTTI Definitions | |
| mormot.core.test | Framework Core Unit and Regression Testing | |
| mormot.core.text | Framework Core Low-Level Text Processing | |
| mormot.core.threads | Framework Core Multi-Threading Support | |
| mormot.core.unicode | Framework Core Low-Level Unicode UTF-8 UTF-16 Ansi Conversion | |
| mormot.core.variants | Framework Core Low-Level Variants / TDocVariant process |
| Objects | Description | |
|---|---|---|
| EInterfaceFactory | Exception dedicated to interface factory, used e.g. for services and mock/stubs | |
| EInterfaceResolver | Exception raised in case of Dependency Injection (aka IoC) issue | |
| EInterfaceStub | Exception class raised during dependency stubing/mocking process | |
| TFakeCallContext | Raw execution context for TInterfacedObjectFakeRaw.FakeCall*() methods | |
| TFakeCallStack | Map the stack memory layout at TInterfacedObjectFake.FakeCall() | |
| TInjectableObject | Any service implementation class could inherit from this class to allow dependency injection aka SOLID DI/IoC by the framework | |
| TInterfacedObjectFake | Instances of this class will emulate a given interface over JSON content | |
| TInterfacedObjectFakeCallback | Abstract class defining a FakeInvoke() virtual method via a TOnFakeInstanceInvoke signature | |
| TInterfacedObjectFakeRaw | Abstract class handling a generic interface implementation class | |
| TInterfaceFactory | Class handling interface RTTI and fake implementation class | |
| TInterfaceFactoryArgument | Index-based reference to one TInterfaceFactory argument | |
| TInterfaceFactoryGenerated | Class handling interface implementation generated from source | |
| TInterfaceFactoryRtti | Class handling interface RTTI and fake implementation class | |
| TInterfaceMethod | Describe an interface-based service provider method | |
| TInterfaceMethodArgument | Describe a service provider method argument | |
| TInterfaceMethodExecute | Execute a method of a TInterfacedObject instance, from/to JSON | |
| TInterfaceMethodExecuteCached | Reusable interface method execution from/to JSON | |
| TInterfaceMethodExecuteRaw | Abtract execution of a TInterfacedObject method | |
| TInterfaceMock | Used to mock an interface implementation via expect-run-verify pattern | |
| TInterfaceMockSpy | Used to mock an interface implementation via run-verify pattern | |
| TInterfaceResolver | Abstract factory class allowing to call interface resolution in cascade | |
| TInterfaceResolverForSingleInterface | Abstract factory class targetting a single kind of interface | |
| TInterfaceResolverInjected | Abstract factory class targetting any kind of interface | |
| TInterfaceResolverList | Register a thread-safe list of classes to implement some interfaces | |
| TInterfaceResolverListEntry | How TInterfaceResolverList store one interface/class | |
| TInterfaceStub | Used to stub an interface implementation | |
| TInterfaceStubLog | Used to keep track of one stubbed method call | |
| TInterfaceStubRule | Define a mocking / stubing rule used internally by TInterfaceStub | |
| TInterfaceStubRules | Define the rules for a given method as used internally by TInterfaceStub | |
| TOnInterfaceStubExecuteParamsAbstract | Abstract parameters used by TInterfaceStub.Executes() events callbacks | |
| TOnInterfaceStubExecuteParamsJson | Parameters used by TInterfaceStub.Executes() events callbacks as JSON | |
| TOnInterfaceStubExecuteParamsVariant | Parameters used by TInterfaceStub.Executes() events callbacks as Variant | |
| TServiceCustomAnswer | A record type to be used as result for a function method for custom content for interface-based services |
TInterfaceMethodArgument = object(TObject)
Describe a service provider method argument
ArgRtti: TRttiJson;
The low-level RTTI information of this argument
- use ArgRtti.Info to retrieve the TypeInfo() of this argument
ArgTypeName: PShortString;
The type name, as declared in object pascal
FPRegisterIdent: byte;
Specify if a floating-point argument is passed as register
- i386/x87: contains always 0
- x86_64: 1 for XMM0, 2 for XMM1, , ..., 8 for XMM7
- ARMHF: 1 for D0, 2 for D1, ..., 8 for D7
- AARCH64: 1 for V0, 2 for V1, ..., 8 for V7
IndexVar: byte;
Index of the associated variable in the local array[ArgsUsedCount[]]
InStackOffset: smallint;
Byte offset in the CPU stack of this argument (16-bit)
- may be -1 if pure register parameter with no backup on stack (x86)
OffsetAsValue: cardinal;
64-bit aligned position in TInterfaceMethod.ArgsSizeAsValue memory
ParamName: PShortString;
The argument name, as declared in object pascal
RegisterIdent: byte;
Specify if the argument is passed as register
- contains 0 if parameter is not a register
- i386: 1 for EAX, 2 for EDX and 3 for ECX registers
- x86_64: 1=RCX/RDI 2=RDX/RSI 3=R8/RDX 4=R9/RCX, with stack backing store
- ARM: 1=R0 2=R1 3=R2 4=R3, with a backing store on the stack
- AARCH64: 1=X0 2=X1, ..., 8=X7, with a backing store on the stack
SizeInStack: byte;
Size (in bytes) of this argument on the stack
ValueDirection: TInterfaceMethodValueDirection;
The variable direction as defined at code level
ValueKindAsm: TInterfaceMethodValueAsm;
How the variable is to be passed at asm level
ValueType: TInterfaceMethodValueType;
We do not handle all kind of object pascal variables
ValueVar: TInterfaceMethodValueVar;
How the variable may be stored
function SetFromJson(var Ctxt: TJsonParserContext; Method: PInterfaceMethod; V: pointer; Error: PShortString): boolean;
Unserialize a JSON value into this argument
procedure AddAsVariant(var Dest: TDocVariantData; V: pointer);
Add a value into a TDocVariant object or array
- Dest should already have set its Kind to either dvObject or dvArray
procedure AddDefaultJson(WR: TJsonWriter);
Append the default JSON value corresponding to this argument
- includes a pending ','
procedure AddJson(WR: TJsonWriter; V: pointer; ObjectOptions: TTextWriterWriteObjectOptions = [woDontStoreDefault]);
Append the JSON value corresponding to this argument
procedure AddJsonEscaped(WR: TJsonWriter; V: pointer);
Append the value corresponding to this argument as within a JSON string
- will escape any JSON string character, and include a pending ','
procedure AddValueJson(WR: TJsonWriter; const Value: RawUtf8);
Append the JSON value corresponding to this argument, from its text value
- includes a pending ','
procedure FixValue(var Value: variant);
Normalize a value containing one input or output argument
- sets and enumerates will be translated to strings (also in embedded objects and T*ObjArray)
procedure FixValueAndAddToObject(const Value: variant; var DestDoc: TDocVariantData);
Normalize a value containing one input or output argument, and add it to a destination variant Document
- sets and enumerates will be translated to strings (also in embedded objects and T*ObjArray)
procedure SerializeToContract(WR: TJsonWriter);
Serialize the argument into the TServiceContainer.Contract JSON format
- non standard types (e.g. class, enumerate, dynamic array or record) are identified by their type identifier - so contract does not extend up to the content of such high-level structures
TInterfaceMethod = object(TObject)
Describe an interface-based service provider method
Args: TInterfaceMethodArgumentDynArray;
Describe expected method arguments
- Args[0] always is imvSelf
- if method is a function, an additional imdResult argument is appended
ArgsInFirst: shortint;
The index of the first const / var argument in Args[] (signed 8-bit)
ArgsInLast: shortint;
The index of the last const / var argument in Args[] (signed 8-bit)
ArgsInputIsOctetStream: boolean;
True if there is a single input parameter as RawByteString/RawBlob
- TRestRoutingRest.ExecuteSoaByInterface will identify binary input with mime-type 'application/octet-stream' as expected
ArgsInputValuesCount: byte;
The number of const / var parameters in Args[]
- i.e. the number of elements in the input JSON array
ArgsManagedCount: byte;
How manual stack initialization arguments are defined
- set for Args[].ValueVar >= imvvRawUtf8
ArgsManagedFirst: shortint;
The index of the first argument expecting manual stack initialization
- set for Args[].ValueVar >= imvvRawUtf8
ArgsNotResultLast: shortint;
The index of the last argument in Args[], excepting result (signed 8-bit)
ArgsOutFirst: shortint;
The index of the first var / out / result argument in Args[] (signed 8-bit)
ArgsOutLast: shortint;
The index of the last var / out / result argument in Args[] (signed 8-bit)
ArgsOutNotResultLast: shortint;
The index of the last var / out argument in Args[] (signed 8-bit)
ArgsOutputValuesCount: byte;
The number of var / out parameters + in Args[]
- i.e. the number of elements in the output JSON array or object
ArgsResultIndex: shortint;
The index of the result pseudo-argument in Args[] (signed 8-bit)
- is -1 if the method is defined as a procedure (not a function)
ArgsResultIsServiceCustomAnswer: boolean;
True if the result is a TServiceCustomAnswer record
- that is, a custom Header+Content BLOB transfert, not a JSON object
ArgsResultIsServiceCustomStatus: boolean;
True if the result is a TServiceCustomStatus 32-bit integer
- that is, a custom HTTP status
ArgsSizeAsValue: cardinal;
64-bit aligned cumulative size for all arguments values
- follow Args[].OffsetAsValue distribution
ArgsSizeInStack: word;
Needed CPU stack size (in bytes) for all arguments
- under x64, does not include the backup space for the four registers
ArgsUsed: TInterfaceMethodValueTypes;
Contains all used kind of arguments
ArgsUsedCount: array[TInterfaceMethodValueVar] of byte;
Contains the count of variables for all used kind of arguments
DefaultResult: RawUtf8;
The method default result, formatted as a JSON array
- example of content may be '[]' for a procedure or '[0]' for a function
- any var/out and potential function result will be set as a JSON array of values, with 0 for numerical values, "" for textual values, false for booleans, [] for dynamic arrays, a void record serialized as expected (including customized serialization) and null for objects
ExecutionMethodIndex: byte;
Method index in the original (non emulated) interface
- our custom methods start at index 3 (RESERVED_VTABLE_SLOTS), since QueryInterface, _AddRef, and _Release are always defined by default
- so it maps TServiceFactory.Interface.Methods[ExecutionMethodIndex-3]
HasSpiParams: TInterfaceMethodValueDirections;
The directions of arguments with SPI parameters defined
HierarchyLevel: byte;
Is 0 for the root interface, 1..n for all inherited interfaces
InterfaceDotMethodName: RawUtf8;
The fully qualified dotted method name, including the interface name
- as used by TServiceContainerInterfaceMethod.InterfaceDotMethodName
- match the URI fullpath name, e.g. 'Calculator.Add'
IsInherited: boolean;
TRUE if the method is inherited from another parent interface
Uri: RawUtf8;
The method URI, i.e. the method name
- as declared in object pascal code, e.g. 'Add' for ICalculator.Add
- this property value is hashed internally for faster access
function ArgIndex(ArgName: PUtf8Char; ArgNameLen: integer; Input: boolean): PtrInt;
Retrieve an argument index in Args[] from its name
- search is case insensitive
- if Input is TRUE, will search within const / var arguments
- if Input is FALSE, will search within var / out / result arguments
- returns -1 if not found
function ArgNextInput(var arg: integer): boolean;
Find the next input (const / var) argument index in Args[]
- returns true if arg is the new value, false otherwise
function ArgNextOutput(var arg: integer): boolean;
Find the next output (var / out / result) argument index in Args[]
- returns true if arg is the new value, false otherwise
function ArgsArrayToObject(P: PUtf8Char; Input: boolean): RawUtf8;
Convert parameters encoded as a JSON array into a JSON object
- if Input is TRUE, will handle const / var arguments
- if Input is FALSE, will handle var / out / result arguments
function ArgsCommandLineToObject(P: PUtf8Char; Input: boolean; RaiseExceptionOnUnknownParam: boolean = false): RawUtf8;
Convert parameters encoded as name=value or name='"value"' or name='{somejson}' into a JSON object
- on Windows, use double-quotes ("") anywhere you expect single-quotes (")
- as expected e.g. from a command line tool
- if Input is TRUE, will handle const / var arguments
- if Input is FALSE, will handle var / out / result arguments
function ArgsNames(Input: boolean): TRawUtf8DynArray;
Returns a dynamic array list of all parameter names
- if Input is TRUE, will handle const / var arguments
- if Input is FALSE, will handle var / out / result arguments
procedure ArgsAsDocVariantFix(var ArgsObject: TDocVariantData; Input: boolean);
Normalize a TDocVariant containing the input or output arguments values
- "normalization" will ensure sets and enums are seralized as text
- if Input is TRUE, will handle const / var arguments
- if Input is FALSE, will handle var / out / result arguments
procedure ArgsAsDocVariantObject(const ArgsParams: TDocVariantData; var ArgsObject: TDocVariantData; Input: boolean);
Convert a TDocVariant array containing the input or output arguments values in order, into an object with named parameters
- here sets and enums will keep their current values, mainly numerical
- if Input is TRUE, will handle const / var arguments
- if Input is FALSE, will handle var / out / result arguments
procedure ArgsStackAsDocVariant(Values: PPointerArray; out Dest: TDocVariantData; Input: boolean);
Computes a TDocVariant containing the input or output arguments values
- Values[] should point to the input/output raw binary values, as stored in TInterfaceMethodExecute.Values during execution
procedure ArgsValuesAsDocVariant(Kind: TInterfaceMethodParamsDocVariantKind; out Dest: TDocVariantData; const Values: TVariantDynArray; Input: boolean; Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty, dvoValueCopiedByReference]);
Computes a TDocVariant containing the input or output arguments values
- Values[] should contain the input/output raw values as variant
- Kind will specify the expected returned document layout
EInterfaceFactory = class(ESynException)
Exception dedicated to interface factory, used e.g. for services and mock/stubs
TInterfaceFactoryArgument = record
Index-based reference to one TInterfaceFactory argument
ArgIndex: byte;
The index of the method argument in TInterfaceFactory.Methods[].Args[]
MethodIndex: byte;
The index of the method argument in TInterfaceFactory.Methods[]
TInterfaceFactory = class(TObject)
Class handling interface RTTI and fake implementation class
- an internal JIT compiler will generate the raw asm opcodes to redirect any interface execution into a fake class
- a thread-safe global list of such class instances is implemented to cache information for better speed: use class function TInterfaceFactory.Get() and not manual TInterfaceFactory.Create / Free
- if you want to search the interfaces by name or TGuid, call once Get(TypeInfo(IMyInterface)) or RegisterInterfaces() for proper registration
- will use TInterfaceFactoryRtti classes generated from compiler RTTI
constructor Create(aInterface: PRttiInfo);
Initialize the internal properties from the supplied interface RTTI
- it will check and retrieve all methods of the supplied interface, and prepare all internal structures for later use
- do not call this constructor directly, but TInterfaceFactory.Get()
function CheckMethodIndex(aMethodName: PUtf8Char): integer; overload;
Find the index of a particular method in internal Methods[] list
- won't find the default AddRef/Release/QueryInterface methods
- will raise an EInterfaceFactory if the method is not known
function CheckMethodIndex(const aMethodName: RawUtf8): PtrInt; overload;
Find the index of a particular method in internal Methods[] list
- won't find the default AddRef/Release/QueryInterface methods
- will raise an EInterfaceFactory if the method is not known
function FindFullMethodIndex(const aFullMethodName: RawUtf8; alsoSearchExactMethodName: boolean = false): integer;
Find the index of a particular interface.method in internal Methods[] list
- will search for a match against Methods[].InterfaceDotMethodName property
- won't find the default AddRef/Release/QueryInterface methods
- will return -1 if the method is not known
function FindMethod(const aMethodName: RawUtf8): PInterfaceMethod;
Find a particular method in internal Methods[] list
- just a wrapper around FindMethodIndex() returing a PInterfaceMethod
- will return nil if the method is not known
function FindMethodIndex(const aMethodName: RawUtf8): PtrInt;
Find the index of a particular method in internal Methods[] list
- will search for a match against Methods[].Uri property
- won't find the default AddRef/Release/QueryInterface methods, nor the _free_/_instance_/... pseudo-methods
- will return -1 if the method is not known
- if aMethodName does not have an exact method match, it will try with a trailing underscore, so that e.g. /service/start will match IService._Start()
class function Get(aInterface: PRttiInfo): TInterfaceFactory; overload;
This is the main entry point to the global interface factory cache
- access to this method is thread-safe
- this method will also register the class to further retrieval
class function Get(const aInterfaceName: RawUtf8): TInterfaceFactory; overload;
Retrieve an interface factory from cache, from its name (e.g. 'IMyInterface')
- access to this method is thread-safe
- you shall have registered the interface by a previous call to the overloaded Get(TypeInfo(IMyInterface)) method or RegisterInterfaces()
- if the supplied TGuid has not been previously registered, returns nil
class function Get(const aGuid: TGuid): TInterfaceFactory; overload;
Retrieve an interface factory from cache, from its TGuid
- access to this method is thread-safe
- you shall have registered the interface by a previous call to the overloaded Get(TypeInfo(IMyInterface)) method or RegisterInterfaces()
- if the supplied TGuid has not been previously registered, returns nil
function GetFullMethodName(aMethodIndex: integer): RawUtf8;
Returns the full 'Interface.MethodName' text, from a method index
- the method index should start at 0 for _free_/_contract_/_signature_ pseudo-methods, and start at index 3 for real Methods[]
- will return plain 'Interface' text, if aMethodIndex is incorrect
function GetMethodName(MethodIndex: integer): RawUtf8;
Returns the method name from its method index
- the method index should start at 0 for _free_/_contract_/_signature_ pseudo-methods, and start at index 3 for real Methods[]
class function GetUsedInterfaces: TSynObjectListLightLocked;
Returns the list of all declared TInterfaceFactory
- as used by SOA and mocking/stubing features of this unit
class function Guid2TypeInfo(const aGuids: array of TGuid): PRttiInfoDynArray; overload;
Could be used to retrieve an array of TypeInfo() from their Guid
class function Guid2TypeInfo(const aGuid: TGuid): PRttiInfo; overload;
Could be used to retrieve an array of TypeInfo() from their Guid
class procedure AddToObjArray(var Obj: TInterfaceFactoryObjArray; const aGuids: array of TGuid);
Add some TInterfaceFactory instances from their Guid
procedure CheckMethodIndexes(const aMethodName: array of RawUtf8; aSetAllIfNone: boolean; out aBits: TInterfaceFactoryMethodBits);
Set the Methods[] indexes bit from some methods names
- won't find the default AddRef/Release/QueryInterface methods
- will raise an EInterfaceFactory if the method is not known
class procedure RegisterInterfaces(const aInterfaces: array of PRttiInfo);
Register one or several interfaces to the global interface factory cache
- so that you can use TInterfaceFactory.Get(aGuid) or Get(aName)
class procedure RegisterUnsafeSpiType(const Types: array of PRttiInfo);
Register some TypeInfo() containing unsafe parameter values
- i.e. any RTTI type containing Sensitive Personal Information, e.g. a bank card number or a plain password
- such values will force associated values to be ignored during loging, as a more tuned alternative to optNoLogInput or optNoLogOutput
property ArgUsed: TInterfaceFactoryPerArgumentDynArray read fArgUsed;
Reference all known interface arguments per value type
property Contract: RawUtf8 read fContract;
The service contract as a JSON array
property DocVariantOptions: TDocVariantOptions read fDocVariantOptions write fDocVariantOptions;
How this interface will work with variants (including TDocVariant)
- by default, contains JSON_FAST_FLOAT for best performance - i.e. [dvoReturnNullForUnknownProperty,dvoValueCopiedByReference, dvoAllowDoubleValue]
property InterfaceIID: TGuid read fInterfaceIID;
The registered Interface Guid
property InterfaceName: RawUtf8 read fInterfaceName;
Will return the interface name, e.g. 'ICalculator'
- published property to be serializable as JSON e.g. for debbuging info
property InterfaceRtti: TRttiJson read fInterfaceRtti;
The registered Interface high-level compiler RTTI type
property InterfaceTypeInfo: PRttiInfo read fInterfaceTypeInfo;
The registered Interface low-level compiler RTTI type
property InterfaceUri: RawUtf8 read fInterfaceUri write fInterfaceUri;
The interface name, without its initial 'I'
- e.g. ICalculator -> 'Calculator'
property JsonParserOptions: TJsonParserOptions read fJsonParserOptions write fJsonParserOptions;
How this interface will process its JSON parsing
- by default, contains JSONPARSER_SERVICE very relaxed parsing options
property MethodIndexCallbackReleased: integer read fMethodIndexCallbackReleased;
Identifies a CallbackReleased() method in this interface
- i.e. the index in Methods[] of the following signature:
procedure CallbackReleased(const callback: IInvokable; const interfaceName: RawUtf8);
- this method will be called e.g. by TInterfacedCallback.Destroy, when a callback is released on the client side so that you may be able e.g. to unsubscribe the callback from an interface list (via InterfaceArrayDelete)
- contains -1 if no such method do exist in the interface definition
property MethodIndexCurrentFrameCallback: integer read fMethodIndexCurrentFrameCallback;
Identifies a CurrentFrame() method in this interface
- i.e. the index in Methods[] of the following signature:
procedure CurrentFrame(isLast: boolean);
- this method will be called e.g. by TRestHttpClientWebsockets.CallbackRequest for interface callbacks in case of WebSockets jumbo frames, to allow e.g. faster database access via a batch
- contains -1 if no such method do exist in the interface definition
property Methods: TInterfaceMethodDynArray read fMethods;
The declared internal methods
- list does not contain default AddRef/Release/QueryInterface methods
- nor the _free_/_contract_/_signature_ pseudo-methods
property MethodsCount: integer read fMethodsCount;
The number of internal methods
- does not include the default AddRef/Release/QueryInterface methods
- nor the _free_/_contract_/_signature_ pseudo-methods: so you should add SERVICE_PSEUDO_METHOD_COUNT to compute the regular MethodIndex
TInterfaceFactoryRtti = class(TInterfaceFactory)
Class handling interface RTTI and fake implementation class
- this class only exists for Delphi 6 and up, and newer FPC, which has the expected RTTI - see http://bugs.freepascal.org/view.php?id=26774
TInterfaceFactoryGenerated = class(TInterfaceFactory)
Class handling interface implementation generated from source
- this class targets oldest FPC, which did not generate the expected RTTI - see http://bugs.freepascal.org/view.php?id=26774
- mormot.soa.codegen.pas will generate a new inherited class, overriding abstract AddMethodsFromTypeInfo() to define the interface methods
class procedure RegisterInterface(aInterface: PRttiInfo); virtual;
Register one interface type definition from the current class
- will be called by mormot.soa.codegen generated code, in initialization section, so that the needed type information will be available
TServiceCustomAnswer = record
A record type to be used as result for a function method for custom content for interface-based services
- all answers are pure JSON object by default: using this kind of record as result will allow a response of any type (e.g. binary, HTML or text)
- this kind of answer will be understood by our TServiceContainerClient implementation, and it may be used with plain AJAX or HTML requests (via POST), to retrieve some custom content
Content: RawByteString;
The response body
- corresponding to the response type, as defined in Header
Header: RawUtf8;
Mandatory response type, as encoded in the HTTP header
- set the response mime-type - use e.g. JSON_CONTENT_TYPE_HEADER_VAR TEXT_CONTENT_TYPE_HEADER or BINARY_CONTENT_TYPE_HEADER constants or GetMimeContentType() function
- if this field is not set, then JSON_CONTENT_TYPE_HEADER will be forced
Status: cardinal;
The HTTP response code
- if not overriden, will default to HTTP_SUCCESS = 200 on server side
- on client side, will always contain HTTP_SUCCESS = 200 on success, or any error should be handled as expected by the caller (e.g. using TServiceFactoryClient.GetErrorMessage for decoding REST/SOA errors)
EInterfaceResolver = class(ESynException)
Exception raised in case of Dependency Injection (aka IoC) issue
TInterfaceResolver = class(TObject)
Abstract factory class allowing to call interface resolution in cascade
- you can inherit from this class to chain the TryResolve() calls so that several kind of implementations may be asked by a TInjectableObject, e.g. TInterfaceStub, TServiceContainer or TDDDRepositoryRestObjectMapping
- this will implement factory pattern, as a safe and thread-safe DI/IoC
function Implements(aInterface: PRttiInfo): boolean; virtual;
Override this method check if this instance implements aInterface RTTI
- this default implementation will call TryResolve() on a local IInterface which is somewhat slow, and should better be overriden
function Resolve(const aGuid: TGuid; out Obj; aRaiseIfNotFound: ESynExceptionClass = nil): boolean; overload;
Can be used to perform an DI/IoC for a given interface
- you shall have registered the interface TGuid by a previous call to
TInterfaceFactory.RegisterInterfaces([TypeInfo(ICalculator),...])
- returns TRUE and set the Obj variable with a matching instance
- returns FALSE (or raise aRaiseIfNotFound) if aGuid is not available
- can be used as such to resolve an ICalculator interface:
var calc: ICalculator; begin if ServiceContainer.Resolve(ICalculator,cal) then ... use calc methods
function Resolve(aInterface: PRttiInfo; out Obj): boolean; overload;
Can be used to perform an DI/IoC for a given interface
- will search for the supplied interface to its internal list of resolvers
- returns TRUE and set the Obj variable with a matching instance
- can be used as such to resolve an ICalculator interface:
var calc: ICalculator; begin if Catalog.Resolve(TypeInfo(ICalculator),calc) then ... use calc methods
procedure Resolve(const aInterfaces: array of TGuid; const aObjs: array of pointer; aRaiseExceptionIfNotFound: boolean = true); overload;
Can be used to perform several DI/IoC for a given set of interfaces
- here interfaces and instances are provided as TGuid and @Instance
- you shall have registered the interface TGuid by a previous call to
TInterfaceFactory.RegisterInterfaces([TypeInfo(ICalculator),...])
- raise an EServiceException if any interface can't be resolved, unless aRaiseExceptionIfNotFound is set to FALSE
procedure ResolveByPair(const aInterfaceObjPairs: array of pointer; aRaiseExceptionIfNotFound: boolean = true);
Can be used to perform several DI/IoC for a given set of interfaces
- here interfaces and instances are provided as TypeInfo,@Instance pairs
- raise an EServiceException if any interface can't be resolved, unless aRaiseExceptionIfNotFound is set to FALSE
TInterfaceResolverForSingleInterface = class(TInterfaceResolver)
Abstract factory class targetting a single kind of interface
constructor Create(const aInterface: TGuid; aImplementation: TInterfacedObjectClass); overload;
This overriden constructor will check and store the supplied class to implement an interface by TGuid
constructor Create(aInterface: PRttiInfo; aImplementation: TInterfacedObjectClass); overload;
This overriden constructor will check and store the supplied class to implement an interface
function GetOneInstance(out Obj): boolean;
You can use this method to resolve the interface as a new instance
function Implements(aInterface: PRttiInfo): boolean; override;
Check if can resolve the supplied interface RTTI
property ImplementationClass: RawUtf8 read GetImplementationName;
The class name which will implement each repository instance
TInterfaceResolverListEntry = record
How TInterfaceResolverList store one interface/class
ImplementationClass: TRttiCustom;
The associated RTTI - mainly used to call its ClassNewInstance method
Instance: IInterface;
Shared instance
- will be released with the TInterfaceResolverListEntries array
InterfaceEntry: PInterfaceEntry;
Low-level interface VMT information for fast creation
TypeInfo: PRttiInfo;
Contains TypeInfo(ISomeInterface)
TInterfaceResolverList = class(TInterfaceResolver)
Register a thread-safe list of classes to implement some interfaces
- as used e.g. by TInterfaceResolverInjected.RegisterGlobal()
function Implements(aInterface: PRttiInfo): boolean; override;
Check if a given interface can be resolved, from its RTTI
procedure Add(aInterface: PRttiInfo; aImplementation: TInterfacedObject); overload;
Register a given implementation class instance for an interface
- the shared aImplementation instance will be returned for each resolution
- aImplementation will be owned by the internal registration list
procedure Add(aInterface: PRttiInfo; aImplementationClass: TInterfacedObjectClass); overload;
Register a given implementaiton class for an interface
- a new aImplementationClass instance will be created for each resolution
procedure Delete(aInterface: PRttiInfo);
Unregister a given implementation class for an interface
- raise EInterfaceResolver if an TInterfacedObject instance was registered
property Entry: TInterfaceResolverListEntries read fEntry;
Low-level access to the internal registered interface/class list
- should be protected via the Safe locking methods
property OnCreateInstance: TOnResolverCreateInstance read fOnCreateInstance write fOnCreateInstance;
Is called when a new aImplementationClass instance has been created
property Safe: TRWLightLock read fSafe;
Low-level access to the internal lock for thread-safety
TInterfaceResolverInjected = class(TInterfaceResolver)
Abstract factory class targetting any kind of interface
- you can inherit from this class to customize dependency injection (DI/IoC), defining the resolution via InjectStub/InjectResolver/InjectInstance methods, and doing the instance resolution using the overloaded Resolve*() methods
- TServiceContainer will inherit from this class, as the main entry point for interface-based services of the framework (via TRest.Services)
- you can use RegisterGlobal() class method to define some process-wide DI
destructor Destroy; override;
Release all used instances
- including all TInterfaceStub instances as specified to Inject(aStubsByGuid)
- will call _Release on all TInterfacedObject dependencies
function Implements(aInterface: PRttiInfo): boolean; override;
Check if a given interface can be resolved, from its RTTI
procedure DeleteResolver(aResolver: TInterfaceResolver);
Delete a previously registered resolver
- aResolver can be re-registered afterwards
procedure InjectInstance(const aDependencies: array of TInterfacedObject); overload; virtual;
Prepare and setup interface DI/IoC resolution from a TInterfacedObject instance
- any TInterfacedObject declared as dependency will have its reference count increased, and decreased in Destroy
procedure InjectResolver(const aOtherResolvers: array of TInterfaceResolver; OwnOtherResolvers: boolean = false); overload; virtual;
Prepare and setup interface DI/IoC resolution with TInterfaceResolver kind of factory
- e.g. a customized TInterfaceStub/TInterfaceMock, a TServiceContainer, a TDDDRepositoryRestObjectMapping or any factory class
- by default, only TInterfaceStub/TInterfaceMock will be owned by this instance, and released by Destroy - unless you set OwnOtherResolvers
procedure InjectStub(const aStubsByGuid: array of TGuid); overload; virtual;
Prepare and setup interface DI/IoC resolution with some blank TInterfaceStub specified by their TGuid
class procedure RegisterGlobal(aInterface: PRttiInfo; aImplementation: TInterfacedObject); overload;
Define a global instance for interface resolution
- most of the time, you will need a local DI/IoC resolution list; but you may use this method to register a set of shared and global resolution patterns, common to the whole injection process
- the supplied instance will be owned by the global list (incrementing its internal reference count), until it will be released via
RegisterGlobalDelete()
- the supplied instance will be freed in the finalization of this unit, if not previously released via RegisterGlobalDelete()
class procedure RegisterGlobal(aInterface: PRttiInfo; aImplementationClass: TInterfacedObjectClass); overload;
Define a global class type for interface resolution
- most of the time, you will need a local DI/IoC resolution list; but you may use this method to register a set of shared and global resolution patterns, common to the whole injection process
- by default, TAutoLocker and TLockedDocVariant will be registered by this unit to implement IAutoLocker and ILockedDocVariant interfaces
class procedure RegisterGlobalDelete(aInterface: PRttiInfo);
Undefine a global instance for interface resolution
- you can unregister a given instance previously defined via
RegisterGlobal(aInterface,aImplementation)
- if you do not call RegisterGlobalDelete(), the remaning instances will be freed in the finalization of this unit
TInjectableObject = class(TInterfacedObjectWithCustomCreate)
Any service implementation class could inherit from this class to allow dependency injection aka SOLID DI/IoC by the framework
- once created, the framework will call AddResolver() member, so that its Resolve*() methods could be used to inject any needed dependency for lazy dependency resolution (e.g. within a public property getter)
- any interface published property will also be automatically injected
- if you implement a SOA service with this class, TRestServer.Services will be auto-injected via TServiceFactoryServer.CreateInstance()
constructor CreateInjected(const aStubsByGuid: array of TGuid; const aOtherResolvers: array of TInterfaceResolver; const aDependencies: array of TInterfacedObject; aRaiseEServiceExceptionIfNotFound: boolean = true); virtual;
Initialize an instance, defining one or several mean of dependency resolution
- simple TInterfaceStub could be created directly from their TGuid, then any kind of DI/IoC resolver instances could be specified, i.e. either customized TInterfaceStub/TInterfaceMock, a TServiceContainer or a TDDDRepositoryRestObjectMapping, and then any TInterfacedObject instance will be used during dependency resolution:
procedure TMyTestCase.OneTestCaseMethod; var Test: IServiceToBeTested; begin Test := TServiceToBeTested.CreateInjected( [ICalculator], [TInterfaceMock.Create(IPersistence,self). ExpectsCount('SaveItem',qoEqualTo,1), RestInstance.Services], [AnyInterfacedObject]); ...
- note that all the injected stubs/mocks instances will be owned by the TInjectableObject, and therefore released with it
- any TInterfacedObject declared as dependency will have its reference count increased, and decreased in Destroy
- once DI/IoC is defined, will call the AutoResolve() protected method
constructor CreateWithResolver(aResolver: TInterfaceResolver; aRaiseEServiceExceptionIfNotFound: boolean = true); virtual;
Initialize an instance, defining one dependency resolver
- the resolver may be e.g. a TServiceContainer
- once the DI/IoC is defined, will call the AutoResolve() protected method
- as called by TServiceFactoryServer.CreateInstance
destructor Destroy; override;
Release all used instances
- including all TInterfaceStub instances as specified to CreateInjected()
procedure Resolve(const aInterfaces: array of TGuid; const aObjs: array of pointer); overload;
Can be used to perform several DI/IoC for a given set of interfaces
- here interfaces and instances are provided as TGuid and pointers
procedure Resolve(aInterface: PRttiInfo; out Obj); overload;
Can be used to perform an DI/IoC for a given interface type information
procedure Resolve(const aGuid: TGuid; out Obj); overload;
Can be used to perform an DI/IoC for a given interface TGuid
procedure ResolveByPair(const aInterfaceObjPairs: array of pointer);
Can be used to perform several DI/IoC for a given set of interfaces
- here interfaces and instances are provided as TypeInfo,@Instance pairs
property Resolver: TInterfaceResolver read fResolver;
Access to the associated dependency resolver, if any
EInterfaceStub = class(EInterfaceFactory)
Exception class raised during dependency stubing/mocking process
TOnInterfaceStubExecuteParamsAbstract = class(TObject)
Abstract parameters used by TInterfaceStub.Executes() events callbacks
constructor Create(aSender: TInterfaceStub; aMethod: PInterfaceMethod; const aParams, aEventParams: RawUtf8); virtual;
Constructor of one parameters marshalling instance
procedure Error(const Format: RawUtf8; const Args: array of const); overload;
Call this method if the callback implementation failed
procedure Error(const aErrorMessage: RawUtf8); overload;
Call this method if the callback implementation failed
property EventParams: RawUtf8 read fEventParams;
A custom message, defined at TInterfaceStub.Executes() definition
property Failed: boolean read fFailed;
Low-level flag, set to TRUE if one of the Error() method was called
property Method: PInterfaceMethod read fMethod;
Pointer to the method which is to be executed
property result: RawUtf8 read fResult;
Outgoing values array encoded as JSON
- every var, out parameter or the function result shall be encoded as a JSON array into this variable, in the same order than the stubbed method declaration
- use Returns() method to create the JSON array directly, from an array of values
property Sender: TInterfaceStub read fSender;
The stubbing / mocking generator
property TestCase: TSynTestCase read GetSenderAsMockTestCase;
The mocking generator associated test case
- will raise an exception if the associated Sender generator is not a TInterfaceMock
TOnInterfaceStubExecuteParamsVariant = class(TOnInterfaceStubExecuteParamsAbstract)
Parameters used by TInterfaceStub.Executes() events callbacks as Variant
- this class will expect input and output parameters to specified as variant arrays properties, so is easier (and a bit slower) than the TOnInterfaceStubExecuteParamsJson class
constructor Create(aSender: TInterfaceStub; aMethod: PInterfaceMethod; const aParams, aEventParams: RawUtf8); override;
Constructor of one parameters marshalling instance
function InputAsDocVariant(Kind: TInterfaceMethodParamsDocVariantKind; Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty, dvoValueCopiedByReference]): variant;
Returns the input parameters as a TDocVariant object or array
function OutputAsDocVariant(Kind: TInterfaceMethodParamsDocVariantKind; Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty, dvoValueCopiedByReference]): variant;
Returns the output parameters as a TDocVariant object or array
procedure AddLog(aLog: TSynLogClass; aOutput: boolean; aLevel: TSynLogLevel = sllTrace);
Log the input or output parameters to a log instance
property Input[Index: integer]: variant read GetInput;
Input parameters when calling the method
- order shall follow the method const and var parameters
Stub.Add(10,20) -> Input[0]=10, Input[1]=20
- if the supplied Index is out of range, an EInterfaceStub will be raised
property Named[const ParamName: RawUtf8]: variant read GetInNamed write SetOutNamed;
Access to input/output parameters when calling the method
- if the supplied name is incorrect, an EInterfaceStub will be raised
- is a bit slower than Input[]/Output[] indexed properties, but easier to work with, and safer in case of method signature change (like parameter add or rename)
- marked as default property, so you can use it e.g. as such:
procedure TFooTestCase.ExecuteBar(Ctxt: TOnInterfaceStubExecuteParamsVariant); begin Ctxt['i'] := Ctxt['i']+1; // i := i+1; Ctxt['result'] := 42; // result := 42; end;
to emulate this native implementation:
function Bar(var i: integer): integer;
begin
inc(i);
result := 42;
end;- using this default Named[] property is recommended over the index-based Output[] property
- if an Output[]/Named[] item is not set, a default value will be used
property Output[Index: integer]: variant write SetOutput;
Output parameters returned after method process
- order shall follow the method var, out parameters and the function result (if method is not a procedure)
- if the supplied Index is out of range, an EInterfaceStub will be raised
- can be used as such:
procedure TFooTestCase.ExecuteBar(Ctxt: TOnInterfaceStubExecuteParamsVariant); begin // Input[0]=i Ctxt.Output[0] := Ctxt.Input[0]+1; // i := i+1; Ctxt.Output[1] := 42; // result := 42; end; // Output|0]=i, Output[1]=result
to emulate this native implementation:
function Bar(var i: integer): integer;
begin
inc(i);
result := 42;
end;- consider using the safest Named[] property, to avoid parameters index matching issue
- if an Output[]/Named[] item is not set, a default value will be used
property U[const ParamName: RawUtf8]: RawUtf8 read GetInUtf8;
Access to UTF-8 input parameters when calling the method
- if the supplied name is incorrect, an EInterfaceStub will be raised
- is a bit slower than Input[]/Output[] indexed properties, but easier to work with, and safer in case of method signature change (like parameter add or rename)
- slightly easier to use Ctxt.U['str'] than ToUtf8(Ctxt.Named['str'])
TOnInterfaceStubExecuteParamsJson = class(TOnInterfaceStubExecuteParamsAbstract)
Parameters used by TInterfaceStub.Executes() events callbacks as JSON
- this class will expect input and output parameters to be encoded as JSON arrays, so is faster than TOnInterfaceStubExecuteParamsVariant
procedure Returns(const ValuesJsonArray: RawUtf8); overload;
A method to return a JSON array of values into result
- expected format is e.g. '[43,42]'
procedure Returns(const Values: array of const); overload;
A method to return an array of values into result
- just a wrapper around JsonEncodeArrayOfConst([...])
- can be used as such:
procedure TFooTestCase.ExecuteBar(var Ctxt: TOnInterfaceStubExecuteParamsJson); begin // Ctxt.Params := '[i]' -> Ctxt.result := '[i+1,42]' Ctxt.Returns([GetInteger(pointer(Ctxt.Params))+1,42]); end;
to emulate this native implementation:
function Bar(var i: integer): integer;
begin
inc(i);
result := 42;
end;property Params: RawUtf8 read fParams;
Incoming parameters array encoded as JSON array without braces
- order follows the method const and var parameters
Stub.Add(10,20) -> Params = '10,20';
TInterfaceStubRule = record
Define a mocking / stubing rule used internally by TInterfaceStub
ExceptionClass: ExceptClass;
The exception class to be raised
- for TInterfaceStub.Raises(), Values contains Exception.Message
Execute: TMethod;
The event handler to be executed
- for TInterfaceStub.Executes(), Values is transmitted as aResult parameter
- either a TOnInterfaceStubExecuteJson, or a TOnInterfaceStubExecuteVariant
ExpectedPassCount: cardinal;
Expected pass count value set by TInterfaceStub.ExpectsCount()
- value to be compared to the number of times this rule has been executed
- TInterfaceStub/TInterfaceMock will check it in their Destroy destructor, using the comparison stated by ExpectedPassCountOperator
ExpectedPassCountOperator: TInterfaceStubRuleOperator;
Comparison operator set by TInterfaceStub.ExpectsCount()
ExpectedTraceHash: cardinal;
Log trace value set by TInterfaceStub.ExpectsTrace()
- used in conjunction with ExpectedPassCountOperator=ioTraceMatch
- value to be compared to the Hash32() value of the execution log trace
- TInterfaceStub/TInterfaceMock will check it in their Destroy destructor, using the fLogs[] content
Kind: TInterfaceStubRuleKind;
The type of this rule
- isUndefined is used for a TInterfaceStub.ExpectsCount() weak rule
Params: RawUtf8;
Optional expected parameters, serialized as a JSON array
- if equals '', the rule is not parametrized - i.e. it will be the default for this method
RulePassCount: cardinal;
The number of times this rule has been executed
Values: RawUtf8;
Values associated to the rule
- for TInterfaceStub.Executes(), is the aEventParams parameter transmitted to Execute event handler (could be used to e.g. customize the handler)
- for TInterfaceStub.Raises(), is the Exception.Message associated to one ExceptionClass
- for TInterfaceStub.Returns(), is the returned result, serialized as a JSON array (including var / out parameters then any function result)
- for TInterfaceStub.Fails() is the returned error message for TInterfaceStub exception or TInterfaceMock associated test case
TInterfaceStubRules = object(TObject)
Define the rules for a given method as used internally by TInterfaceStub
DefaultRule: integer;
Index in Rules[] of the default rule, i.e. the one with Params=''
MethodPassCount: cardinal;
The number of times this method has been executed
Rules: array of TInterfaceStubRule;
The mocking / stubing rules associated to this method
function FindRuleIndex(const aParams: RawUtf8): integer;
Find a rule index from its Params content
function FindStrongRuleIndex(const aParams: RawUtf8): integer;
Find a strong rule index from its Params content
procedure AddRule(Sender: TInterfaceStub; aKind: TInterfaceStubRuleKind; const aParams, aValues: RawUtf8; const aEvent: TNotifyEvent = nil; aExceptionClass: ExceptClass = nil; aExpectedPassCountOperator: TInterfaceStubRuleOperator = ioUndefined; aValue: cardinal = 0);
Register a rule
TInterfaceStubLog = object(TObject)
Used to keep track of one stubbed method call
CustomResults: RawUtf8;
Any non default result returned after execution
- if not set (i.e. if equals ''), Method^.DefaultResult has been returned
- if WasError is TRUE, always contain the error message
Method: PInterfaceMethod;
The method called
- a pointer to the existing information in shared TInterfaceFactory
Params: RawUtf8;
The parameters at execution call, as JSON CSV (i.e. array without [ ])
Timestamp64: Int64;
Call timestamp, in milliseconds
- is filled with GetTickCount64() API returned value
WasError: boolean;
Set to TRUE if this calls failed
- i.e. if EInterfaceFactory was raised for TInterfaceStub, or if TInterfaceMock did notify its associated TSynTestCase via a Check()
- CustomResults/Results will contain the error message
function Results: RawUtf8;
The result returned after execution
- this method will return Method^.DefaultResult if CustomResults=''
procedure AddAsText(WR: TJsonWriter; aScope: TInterfaceStubLogLayouts; SepChar: AnsiChar = ',');
Append the log in textual format
- typical output is as such:
Add(10,20)=[30],
or, if WasError is TRUE:
Divide(20,0) error "divide by zero",
TInterfaceStub = class(TInterfaceResolver)
Used to stub an interface implementation
- define the expected workflow in a fluent interface using Executes / Fails / Returns / Raises
- this class will be inherited by TInterfaceMock which will contain some additional methods dedicated to mocking behavior (e.g. including in tests)
- each instance of this class will be owned by its generated fake implementation class (retrieved at constructor out parameter): when the stubed/mocked interface is freed, its associated TInterfaceStub will be freed - so you do not need to protect TInterfaceStub.Create with a try..finally clause, since it will be released when no more needed
- inherits from TInterfaceResolver so match TInjectableObject expectations
constructor Create(const aInterfaceName: RawUtf8; out aStubbedInterface); reintroduce; overload;
Initialize an interface stub from an interface name (e.g. 'IMyInterface')
- you shall have registered the interface by a previous call to TInterfaceFactory.Get(TypeInfo(IMyInterface)) or RegisterInterfaces([])
- if the supplied name has not been previously registered, raise an Exception
constructor Create(aInterface: PRttiInfo); reintroduce; overload;
Prepare an interface stub from TypeInfo(IMyInterface) for later injection
- create several TInterfaceStub instances for a given TInjectableObject
procedure TMyTestCase.OneTestCaseMethod;
var Test: IServiceToBeTested;
begin
Test := TServiceToBeTested.CreateInjected([],
TInterfaceStub.Create(TypeInfo(ICalculator)),
TInterfaceMock.Create(TypeInfo(IPersistence),self).
ExpectsCount('SaveItem',qoEqualTo,1)]);constructor Create(const aGuid: TGuid); reintroduce; overload;
Prepare an interface stub from a given TGuid for later injection
- you shall have registered the interface by a previous call to
TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...])
- then create TInterfaceStub instances for a given TInjectableObject:
procedure TMyTestCase.OneTestCaseMethod;
var Test: IServiceToBeTested;
begin
Test := TServiceToBeTested.CreateInjected(
[IMyInterface],
TInterfaceMock.Create(IPersistence,self).
ExpectsCount('SaveItem',qoEqualTo,1)]);constructor Create(aFactory: TInterfaceFactory; const aInterfaceName: RawUtf8); reintroduce; overload; virtual;
Low-level internal constructor
- you should not call this method, but the overloaded alternatives
constructor Create(aInterface: PRttiInfo; out aStubbedInterface); reintroduce; overload;
Initialize an interface stub from TypeInfo(IMyInterface)
- assign the fake class instance to a stubbed interface variable:
var I: ICalculator; ... TInterfaceStub.Create(TypeInfo(ICalculator),I); Check(I.Add(10,20)=0,'Default result');
constructor Create(const aGuid: TGuid; out aStubbedInterface); reintroduce; overload;
Initialize an interface stub from an interface Guid
- you shall have registered the interface by a previous call to
TInterfaceFactory.RegisterInterfaces([TypeInfo(IMyInterface),...])
- once registered, create and use the fake class instance as such:
var I: ICalculator; ... TInterfaceStub.Create(ICalculator,I); Check(I.Add(10,20)=0,'Default result');
- if the supplied TGuid has not been previously registered, raise an Exception
function Executes(const aMethodName: RawUtf8; const aParams: array of const; const aEvent: TOnInterfaceStubExecuteVariant; const aEventParams: RawUtf8 = ''): TInterfaceStub; overload;
Add an execution rule for a given method and a set of parameters, with Variant marshalling
- if execution context matches the supplied aParams value, aEvent is triggered
- optional aEventParams parameter will be transmitted to aEvent handler
- raise an Exception if the method name does not exist for this interface
function Executes(const aMethodName, aParams: RawUtf8; const aEvent: TOnInterfaceStubExecuteVariant; const aEventParams: RawUtf8 = ''): TInterfaceStub; overload;
Add an execution rule for a given method and a set of parameters, with Variant marshalling
- if execution context matches the supplied aParams value, aEvent is triggered
- optional aEventParams parameter will be transmitted to aEvent handler
- raise an Exception if the method name does not exist for this interface
function Executes(aLog: TSynLogClass; aLogLevel: TSynLogLevel; aKind: TInterfaceMethodParamsDocVariantKind): TInterfaceStub; overload;
Will add execution rules for all methods to log the input parameters
- aKind will define how the input parameters are serialized in JSON
function Executes(const aEvent: TOnInterfaceStubExecuteVariant; const aEventParams: RawUtf8 = ''): TInterfaceStub; overload;
Add an execution rule for all methods, with Variant marshalling
- optional aEventParams parameter will be transmitted to aEvent handler
- callback's Ctxt: TOnInterfaceStubExecuteParamsVariant's Method field will identify the executed method
function Executes(const aMethodName, aParams: RawUtf8; const aEvent: TOnInterfaceStubExecuteJson; const aEventParams: RawUtf8 = ''): TInterfaceStub; overload;
Add an execution rule for a given method and a set of parameters, with JSON marshalling
- if execution context matches the supplied aParams value, aEvent is triggered
- optional aEventParams parameter will be transmitted to aEvent handler
- raise an Exception if the method name does not exist for this interface
function Executes(const aMethodName: RawUtf8; const aEvent: TOnInterfaceStubExecuteJson; const aEventParams: RawUtf8 = ''): TInterfaceStub; overload;
Add an execution rule for a given method, with JSON marshalling
- optional aEventParams parameter will be transmitted to aEvent handler
- raise an Exception if the method name does not exist for this interface
function Executes(const aMethodName: RawUtf8; const aEvent: TOnInterfaceStubExecuteVariant; const aEventParams: RawUtf8 = ''): TInterfaceStub; overload;
Add an execution rule for a given method, with Variant marshalling
- optional aEventParams parameter will be transmitted to aEvent handler
- raise an Exception if the method name does not exist for this interface
function Executes(const aMethodName: RawUtf8; const aParams: array of const; const aEvent: TOnInterfaceStubExecuteJson; const aEventParams: RawUtf8 = ''): TInterfaceStub; overload;
Add an execution rule for a given method and a set of parameters, with JSON marshalling
- if execution context matches the supplied aParams value, aEvent is triggered
- optional aEventParams parameter will be transmitted to aEvent handler
- raise an Exception if the method name does not exist for this interface
function ExpectsCount(const aMethodName: RawUtf8; const aParams: array of const; aOperator: TInterfaceStubRuleOperator; aValue: cardinal): TInterfaceStub; overload;
Add a pass count expectation rule for a given method and a set of parameters
- those rules will be evaluated at Destroy execution
- only qoEqualTo..qoGreaterThanOrEqualTo are relevant here
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function ExpectsCount(const aMethodName, aParams: RawUtf8; aOperator: TInterfaceStubRuleOperator; aValue: cardinal): TInterfaceStub; overload;
Add a pass count expectation rule for a given method and a set of parameters
- those rules will be evaluated at Destroy execution
- only qoEqualTo..qoGreaterThanOrEqualTo are relevant here
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function ExpectsCount(const aMethodName: RawUtf8; aOperator: TInterfaceStubRuleOperator; aValue: cardinal): TInterfaceStub; overload;
Add a pass count expectation rule for a given method
- those rules will be evaluated at Destroy execution
- only qoEqualTo..qoGreaterThanOrEqualTo are relevant here
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function ExpectsTrace(const aMethodName, aParams: RawUtf8; aValue: cardinal): TInterfaceStub; overload;
Add a hash-based execution expectation rule for a given method and a set of parameters
- those rules will be evaluated at Destroy execution
- supplied aValue is a Hash32() of the trace in LogAsText format
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function ExpectsTrace(const aMethodName: RawUtf8; aValue: cardinal): TInterfaceStub; overload;
Add a hash-based execution expectation rule for a given method
- those rules will be evaluated at Destroy execution
- supplied aValue is a Hash32() of the trace in LogAsText format
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function ExpectsTrace(aValue: cardinal): TInterfaceStub; overload;
Add a hash-based execution expectation rule for the whole interface
- those rules will be evaluated at Destroy execution
- supplied aValue is a Hash32() of the trace in LogAsText format
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
function ExpectsTrace(const aMethodName, aParams, aValue: RawUtf8): TInterfaceStub; overload;
Add a JSON-based execution expectation rule for a given method and a set of parameters
- those rules will be evaluated at Destroy execution
- supplied aValue is the trace in LogAsText format
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function ExpectsTrace(const aMethodName, aValue: RawUtf8): TInterfaceStub; overload;
Add a JSON-based execution expectation rule for a given method
- those rules will be evaluated at Destroy execution
- supplied aValue is the trace in LogAsText format
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function ExpectsTrace(const aMethodName: RawUtf8; const aParams: array of const; aValue: cardinal): TInterfaceStub; overload;
Add a hash-based execution expectation rule for a given method and a set of parameters
- those rules will be evaluated at Destroy execution
- supplied aValue is a Hash32() of the trace in LogAsText format
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function ExpectsTrace(const aValue: RawUtf8): TInterfaceStub; overload;
Add a JSON-based execution expectation rule for the whole interface
- those rules will be evaluated at Destroy execution
- supplied aValue is the trace in LogAsText format
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
function ExpectsTrace(const aMethodName: RawUtf8; const aParams: array of const; const aValue: RawUtf8): TInterfaceStub; overload;
Add a JSON-based execution expectation rule for a given method and a set of parameters
- those rules will be evaluated at Destroy execution
- supplied aValue is the trace in LogAsText format
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function Fails(const aMethodName, aParams, aErrorMsg: RawUtf8): TInterfaceStub; overload;
Add an error rule for a given method and a set of parameters
- an error will be returned to the caller, with aErrorMsg as message
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function Fails(const aMethodName, aErrorMsg: RawUtf8): TInterfaceStub; overload;
Add an error rule for a given method
- an error will be returned to the caller, with aErrorMsg as message
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function Fails(const aMethodName: RawUtf8; const aParams: array of const; const aErrorMsg: RawUtf8): TInterfaceStub; overload;
Add an error rule for a given method and a set of parameters
- an error will be returned to the caller, with aErrorMsg as message
- it will raise EInterfaceFactory for TInterfaceStub, but TInterfaceMock will push the failure to the associated test case
- raise an Exception if the method name does not exist for this interface
function Implements(aInterface: PRttiInfo): boolean; override;
Check if can resolve the supplied interface RTTI
function LogAsText(SepChar: AnsiChar = ','): RawUtf8;
The stubbed method execution trace converted as text
- typical output is a list of calls separated by commas:
Add(10,20)=[30],Divide(20,0) error "divide by zero"
function Raises(const aMethodName: RawUtf8; aException: ExceptClass; const aMessage: string): TInterfaceStub; overload;
Add an exception rule for a given method
- will create and raise the specified exception for this method
- raise an Exception if the method name does not exist for this interface
function Raises(const aMethodName, aParams: RawUtf8; aException: ExceptClass; const aMessage: string): TInterfaceStub; overload;
Add an exception rule for a given method and a set of parameters
- will create and raise the specified exception for this method, if the execution context matches the supplied aParams value
- raise an Exception if the method name does not exist for this interface
function Raises(const aMethodName: RawUtf8; const aParams: array of const; aException: ExceptClass; const aMessage: string): TInterfaceStub; overload;
Add an exception rule for a given method and a set of parameters
- will create and raise the specified exception for this method, if the execution context matches the supplied aParams value
- raise an Exception if the method name does not exist for this interface
function Returns(const aMethodName, aExpectedResults: RawUtf8): TInterfaceStub; overload;
Add an evaluation rule for a given method
- aExpectedResults JSON array will be returned to the caller
- raise an Exception if the method name does not exist for this interface
function Returns(const aMethodName: RawUtf8; const aExpectedResults: array of const): TInterfaceStub; overload;
Add an evaluation rule for a given method
- aExpectedResults will be returned to the caller after conversion to a JSON array
- raise an Exception if the method name does not exist for this interface
function Returns(const aMethodName: RawUtf8; const aParams, aExpectedResults: array of const): TInterfaceStub; overload;
Add an evaluation rule for a given method and a set of parameters
- aExpectedResults JSON array will be returned to the caller
- raise an Exception if the method name does not exist for this interface
function Returns(const aMethodName, aParams, aExpectedResults: RawUtf8): TInterfaceStub; overload;
Add an evaluation rule for a given method and a set of parameters
- aExpectedResults JSON array will be returned to the caller
- raise an Exception if the method name does not exist for this interface
function SetOptions(Options: TInterfaceStubOptions): TInterfaceStub;
Set the optional stubing/mocking options
- same as the Options property, but in a fluent-style interface
procedure ClearLog;
Reset the internal trace
- Log, LogAsText, LogHash and LogCount will be initialized
property InterfaceFactory: TInterfaceFactory read fInterface;
Access to the registered Interface RTTI information
property LastInterfacedObjectFake: TInterfacedObject read fLastInterfacedObjectFake;
Returns the last created TInterfacedObject instance
- e.g. corresponding to the out aStubbedInterface parameter of Create()
property Log: TInterfaceStubLogDynArray read fLogs;
The stubbed method execution trace items
property LogCount: integer read fLogCount;
The stubbed method execution trace number of items
property LogHash: cardinal read GetLogHash;
The stubbed method execution trace converted as one numerical hash
- returns Hash32(LogAsText)
property Options: TInterfaceStubOptions read fOptions write IntSetOptions;
Optional stubing/mocking options
- you can use the SetOptions() method in a fluent-style interface
TInterfaceMock = class(TInterfaceStub)
Used to mock an interface implementation via expect-run-verify pattern
- TInterfaceStub will raise an exception on Fails(), ExpectsCount() or ExpectsTrace() rule activation, but TInterfaceMock will call TSynTestCase.Check() with no exception with such rules, as expected by a mocked interface
- this class will follow the expect-run-verify pattern, i.e. expectations are defined before running the test, and verification is performed when the instance is released - use TInterfaceMockSpy if you prefer the more explicit run-verify pattern
constructor Create(aInterface: PRttiInfo; aTestCase: TSynTestCase); reintroduce; overload;
Initialize an interface mock from TypeInfo(IMyInterface) for later injection
- aTestCase.Check() will be called in case of mocking failure
constructor Create(const aGuid: TGuid; aTestCase: TSynTestCase); reintroduce; overload;
Initialize an interface mock from TypeInfo(IMyInterface) for later injection
- aTestCase.Check() will be called in case of mocking failure
constructor Create(const aInterfaceName: RawUtf8; out aMockedInterface; aTestCase: TSynTestCase); reintroduce; overload;
Initialize an interface mock from an interface name (e.g. 'IMyInterface')
- aTestCase.Check() will be called in case of mocking failure
- you shall have registered the interface by a previous call to TInterfaceFactory.Get(TypeInfo(IMyInterface)) or RegisterInterfaces()
- if the supplied name has not been previously registered, raise an Exception
constructor Create(aInterface: PRttiInfo; out aMockedInterface; aTestCase: TSynTestCase); reintroduce; overload;
Initialize an interface mock from TypeInfo(IMyInterface)
- aTestCase.Check() will be called in case of mocking failure
procedure TMyTestCase.OneTestCaseMethod; var Persist: IPersistence; ... TInterfaceMock.Create(TypeInfo(IPersistence),Persist,self). ExpectsCount('SaveItem',qoEqualTo,1)]);
constructor Create(const aGuid: TGuid; out aMockedInterface; aTestCase: TSynTestCase); reintroduce; overload;
Initialize an interface mock from an interface TGuid
- aTestCase.Check() will be called during validation of all Expects*()
- you shall have registered the interface by a previous call to
TInterfaceFactory.RegisterInterfaces([TypeInfo(IPersistence),...])
- once registered, create and use the fake class instance as such:
procedure TMyTestCase.OneTestCaseMethod; var Persist: IPersistence; ... TInterfaceMock.Create(IPersistence,Persist,self). ExpectsCount('SaveItem',qoEqualTo,1)]);
- if the supplied TGuid has not been previously registered, raise an Exception
property TestCase: TSynTestCase read fTestCase;
The associated test case
TInterfaceMockSpy = class(TInterfaceMock)
Used to mock an interface implementation via run-verify pattern
- this class will implement a so called "test-spy" mocking pattern, i.e. no expectation is to be declared at first, but all calls are internally logged (i.e. it force imoLogMethodCallsAndResults option to be defined), and can afterwards been check via Verify() calls
constructor Create(aFactory: TInterfaceFactory; const aInterfaceName: RawUtf8); override;
This will set and force imoLogMethodCallsAndResults option as needed
- you should not call this method, but the overloaded alternatives
procedure Verify(const aMethodName, aTrace: RawUtf8; aScope: TInterfaceMockSpyCheck); overload;
Check an execution trace for a specified method
- text trace format will follow specified scope, e.g.
Verify('Add','(10,30),(2,35)',chkNameParams);
or include parameters and function results:
Verify('Add','(10,30)=[300],(2,35)=[37]',chkNameParamsResults);
- if aMethodName does not exists or aScope=chkName, will raise an exception
procedure Verify(const aMethodName, aParams, aTrace: RawUtf8); overload;
Check an execution trace for a specified method and parameters
- text trace format shall contain only results, e.g.
Verify('Add','2,35','[37]');
procedure Verify(const aMethodName: RawUtf8; const aParams: array of const; const aTrace: RawUtf8); overload;
Check an execution trace for a specified method and parameters
- text trace format shall contain only results, e.g.
Verify('Add',[2,35],'[37]');
procedure Verify(const aTrace: RawUtf8; aScope: TInterfaceMockSpyCheck); overload;
Check an execution trace for the global interface
- text trace format shall follow method calls, e.g.
Verify('Multiply,Add',chkName);
or may include parameters:
Verify('Multiply(10,30),Add(2,35)',chkNameParams);
or include parameters and function results:
Verify('Multiply(10,30)=[300],Add(2,35)=[37]',chkNameParamsResults);
procedure Verify(const aMethodName: RawUtf8; aOperator: TInterfaceStubRuleOperator = ioGreaterThan; aCount: cardinal = 0); overload;
Check that a method has been called a specify number of times
procedure Verify(const aMethodName, aParams: RawUtf8; aOperator: TInterfaceStubRuleOperator = ioGreaterThan; aCount: cardinal = 0); overload;
Check a method calls count with a set of parameters
- parameters shall be defined as a JSON array of values
procedure Verify(const aMethodName: RawUtf8; const aParams: array of const; aOperator: TInterfaceStubRuleOperator = ioGreaterThan; aCount: cardinal = 0); overload;
Check a method calls count with a set of parameters
- parameters shall be defined as a JSON array of values
TFakeCallStack = packed record
Map the stack memory layout at TInterfacedObjectFake.FakeCall()
TFakeCallContext = record
Raw execution context for TInterfacedObjectFakeRaw.FakeCall*() methods
ServiceCustomAnswerPoint: PServiceCustomAnswer;
Type of value stored into result
TInterfacedObjectFakeRaw = class(TInterfacedObject)
Abstract class handling a generic interface implementation class
- implements a simple cross-CPU JIT engine to redirect to FakeCall
- note: inheriting from TSynInterfacedObject is not feasible
constructor Create(aFactory: TInterfaceFactory); reintroduce;
Create an instance, using the specified interface
procedure Get(out Obj);
Retrieve one instance of this interface, increasing its RefCount
procedure GetNoAddRef(out Obj);
Retrieve one instance of this interface, without increasing its RefCount
property Factory: TInterfaceFactory read fFactory;
The associated interface factory class
TInterfacedObjectFake = class(TInterfacedObjectFakeRaw)
Instances of this class will emulate a given interface over JSON content
- as used e.g. by TInterfaceFactoryClient.CreateFakeInstance
constructor Create(aFactory: TInterfaceFactory; aServiceFactory: TObject; aOptions: TInterfacedObjectFakeOptions; const aInvoke: TOnFakeInstanceInvoke; const aNotifyDestroy: TOnFakeInstanceDestroy); reintroduce;
Create an instance, using the specified interface and factory
destructor Destroy; override;
Release the remote server instance (in sicClientDriven mode);
property FakeID: TInterfacedObjectFakeID read fFakeID;
How TInterfacedObjectFake identify this instance
- match the ID used in sicClientDriven mode of a service
- match the TInterfacedObjectFakeServer 32-bit identifier of a callback
TInterfacedObjectFakeCallback = class(TInterfacedObjectFake)
Abstract class defining a FakeInvoke() virtual method via a TOnFakeInstanceInvoke signature
TInterfaceMethodExecuteRaw = class(TObject)
Abtract execution of a TInterfacedObject method
constructor Create(aFactory: TInterfaceFactory; aMethod: PInterfaceMethod; const aOptions: TInterfaceMethodOptions); virtual;
Initialize the execution instance
procedure AddInterceptor(const Hook: TOnInterfaceMethodExecute);
Allow to hook method execution
- if optInterceptInputOutput is defined in Options, then Sender.Input/Output fields will contain the execution data context when Hook is called
procedure AddInterceptors(const Hook: TInterfaceMethodExecuteEventDynArray);
Allow to hook method execution
- if optInterceptInputOutput is defined in Options, then Sender.Input/Output fields will contain the execution data context when Hook[] are called
property BackgroundExecutionThread: TSynBackgroundThreadMethod read fBackgroundExecutionThread write fBackgroundExecutionThread;
Reference to the background execution thread, if any
property CurrentStep: TInterfaceMethodExecuteEventStep read fCurrentStep write fCurrentStep;
The current state of the execution
property ExecutedInstancesFailed: TRawUtf8DynArray read fExecutedInstancesFailed;
Contains exception serialization after ExecuteJson of multiple instances
- follows the Instances[] order as supplied to RawExecute/ExecuteJson
- if only a single Instances[] is supplied, the exception will be propagated to the caller, unless optIgnoreException option is defined
- if more than one Instances[] is supplied, any raised Exception will be serialized using ObjectToJsonDebug(), or this property will be left to its default nil content if no exception occurred
property Factory: TInterfaceFactory read fFactory;
Low-level direct access to the associated interface factory information
property Input: TDocVariantData read fInput;
Set if optInterceptInputOutput is defined in TServiceFactoryServer.Options
- contains a dvObject with input parameters as "argname":value pairs
- this is a read-only property: you cannot change the input content
property LastException: Exception read fLastException;
Only set during AddInterceptor() callback execution, if Step is smsError
property Method: PInterfaceMethod read fMethod;
Low-level direct access to the associated method information
property OnExecute: TInterfaceMethodExecuteEventDynArray read fOnExecute;
Reference to the actual execution method callbacks
property Options: TInterfaceMethodOptions read fOptions write SetOptions;
Associated settings, as copied from TServiceFactoryServer.Options
property Output: TDocVariantData read fOutput;
Set if optInterceptInputOutput is defined in TServiceFactoryServer.Options
- contains a dvObject with output parameters as "argname":value pairs
- this is a read-only property: you cannot change the output content
property Values: PPointerArray read fValues;
Low-level direct access to the current input/output parameter values
- you should not need to access this, but rather set optInterceptInputOutput in Options, and read Input/Output content
TInterfaceMethodExecute = class(TInterfaceMethodExecuteRaw)
Execute a method of a TInterfacedObject instance, from/to JSON
destructor Destroy; override;
Finalize the execution instance
function ExecuteJson(const Instances: array of pointer; P: PUtf8Char; Res: TJsonWriter; Error: PShortString = nil; ResAsJsonObject: boolean = false): boolean;
Execute the corresponding method of weak IInvokable references
- will retrieve a JSON array of parameters from P buffer (as [1,"par2",3])
- will append a JSON array of results in Res, or set an Error message, or a JSON object (with parameter names) in Res if ResultAsJsonObject is set
- if one Instances[] is supplied, any exception will be propagated (unless optIgnoreException is set); if more than one Instances[] is supplied, corresponding ExecutedInstancesFailed[] property will be filled with the JSON serialized exception
function ExecuteJsonCallback(Instance: pointer; const params: RawUtf8; output: PRawUtf8): boolean;
Execute the corresponding method of one weak IInvokable reference
- exepect no output argument, i.e. no returned data, unless output is set
- this version will identify TInterfacedObjectFake implementations, and will call directly fInvoke() if possible, to avoid JSON marshalling
- expect params value to be without [ ], just like TOnFakeInstanceInvoke
function ExecuteJsonFake(Instance: pointer; params: PUtf8Char): boolean;
Execute directly TInterfacedObjectFake.fInvoke()
- expect params value to be with [ ], just like ExecuteJson
function TempTextWriter: TJsonWriter;
Allow to use an instance-specific temporary TOrmWriter
property OnCallback: TOnInterfaceMethodExecuteCallback read fOnCallback write fOnCallback;
Points e.g. to TRestServerUriContext.ExecuteCallback which redirects to TServiceContainerServer.GetFakeCallback
property ServiceCustomAnswerHead: RawUtf8 read fServiceCustomAnswerHead write fServiceCustomAnswerHead;
Set from output TServiceCustomAnswer.Header result parameter
property ServiceCustomAnswerStatus: cardinal read fServiceCustomAnswerStatus write fServiceCustomAnswerStatus;
Set from output TServiceCustomAnswer.Status or TServiceCustomStatus result parameter
TInterfaceMethodExecuteCached = class(TInterfaceMethodExecute)
Reusable interface method execution from/to JSON
- used e.g. by TServiceFactoryServer.ExecuteJson or TMvcRendererAbstract.ExecuteCommand
constructor Create(aFactory: TInterfaceFactory; aMethod: PInterfaceMethod; const aOptions: TInterfaceMethodOptions); override;
Initialize the execution instance
destructor Destroy; override;
Finalize this execution context
procedure Acquire(opt: TInterfaceMethodOptions; out exec: TInterfaceMethodExecuteCached; out WR: TJsonWriter);
Will use this instance if possible, or create a temporary one
class procedure Prepare(aFactory: TInterfaceFactory; out Cached: TInterfaceMethodExecuteCachedDynArray);
Initialize a TInterfaceMethodExecuteCachedDynArray of per-method caches
procedure Release(exec: TInterfaceMethodExecuteCached);
Will release this instance if was acquired, or free a temporary one
PInterfaceMethod = ^TInterfaceMethod;
A pointer to an interface-based service provider method description
- since TInterfaceFactory instances are shared in a global list, we can safely use such pointers in our code to refer to a particular method
PInterfaceMethodArgument = ^TInterfaceMethodArgument;
Pointer to a service provider method argument
TInjectableObjectClass = class of TInjectableObject;
Class-reference type (metaclass) of a TInjectableObject type
TInterfacedObjectFakeID = type cardinal;
How TInterfacedObjectFake identify each instance
- match the ID used in sicClientDriven mode of a service
- match the TInterfacedObjectFakeServer 32-bit identifier of a callback
TInterfacedObjectFakeOption = ( ifoJsonAsExtended, ifoDontStoreVoidJson );
How TInterfacedObjectFake will perform its execution
- by default, fInvoke() will receive standard JSON content, unless ifoJsonAsExtended is set, and extended JSON is used
- ifoDontStoreVoidJson will ensure objects and records won't include default void fields in JSON serialization
TInterfacedObjectFakeOptions = set of TInterfacedObjectFakeOption;
Defines how TInterfacedObjectFakeRaw will perform its execution
TInterfacedObjectObjArray = array of TInterfacedObject;
Used to store a list of TInterfacedObject instances
TInterfaceFactoryArgumentDynArray = array of TInterfaceFactoryArgument;
Index-based reference to several TInterfaceFactory argument
TInterfaceFactoryMethodBits = set of 0 .. MAX_METHOD_COUNT - 1;
May be used to store the Methods[] indexes of a TInterfaceFactory
- current implementation handles up to 128 methods, a limit above which "Interface Segregation" principle is obviously broken
TInterfaceFactoryObjArray = array of TInterfaceFactory;
A dynamic array of TInterfaceFactory instances
TInterfaceFactoryPerArgumentDynArray = array[TInterfaceMethodValueType] of TInterfaceFactoryArgumentDynArray;
Per-type reference of TInterfaceFactory arguments
TInterfaceMethodArgumentDynArray = array of TInterfaceMethodArgument;
Describe a service provider method arguments
TInterfaceMethodDynArray = array of TInterfaceMethod;
Describe all mtehods of an interface-based service provider
TInterfaceMethodExecuteCachedDynArray = array of TInterfaceMethodExecuteCached;
Set reusable interface methods execution from/to JSON
TInterfaceMethodExecuteEventDynArray = array of TOnInterfaceMethodExecute;
Store one or several TInterfaceMethodExecute.OnExecute signatures
TInterfaceMethodExecuteEventStep = ( smsUndefined, smsBefore, smsAfter, smsError );
The current step of a TInterfaceMethodExecute.OnExecute call
TInterfaceMethodOption = ( optExecGlobalLocked, optFreeGlobalLocked, optExecLockedPerInterface, optFreeLockedPerInterface, optExecInPerInterfaceThread, optFreeInPerInterfaceThread, optFreeDelayed, optExecInMainThread, optFreeInMainThread, optVariantCopiedByReference, optVariantFloatAllowed, optInterceptInputOutput, optNoLogInput, optNoLogOutput, optErrorOnMissingParam, optForceStandardJson, optDontStoreVoidJson, optIgnoreException, optFreeTimeout );
Possible service provider method options, e.g. about logging or execution
- see TInterfaceMethodOptions for a description of each available option
TInterfaceMethodOptions = set of TInterfaceMethodOption;
Set of per-method execution options for an interface-based service provider
- by default, method executions are concurrent, for better server responsiveness; if you set optExecLockedPerInterface/optFreeLockedPerInterface, all methods of a given interface will be executed within a critical section; if you set optExecGlobalLocked/optFreeGlobalLocked, execution is done within a critical section global to all interfaces
- optExecInMainThread will force the method to be called within a RunningThread.Synchronize() call - it can be used e.g. if your implementation rely heavily on COM servers - by default, service methods are called within the thread which received them, on multi-thread server instances (e.g. TSqlite3HttpServer or TRestServerNamedPipeResponse), for better response time and CPU use (this is the technical reason why service implementation methods have to handle multi-threading safety carefully, e.g. by using TOSLock mutex on purpose) - warning: a Windows Service has no 'main thread' concept, so should not use it
- optFreeInMainThread will force the _Release/Destroy method to be run in the main thread: setting this option for any method will affect the whole service class - is not set by default, for performance reasons
- optExecInPerInterfaceThread and optFreeInPerInterfaceThread will allow creation of a per-interface dedicated thread
- by default _Release is done within the service lock, unless optFreeDelayed is set and instances are released later (e.g. if may take some time)
- optVariantCopiedByReference and optVariantFloatAllowed are used to generate the proper TDocVariantData options
- if optInterceptInputOutput is set, TServiceFactoryServer.AddInterceptor() events will have their Sender.Input/Output values defined
- if optNoLogInput/optNoLogOutput is set, TSynLog and SetServiceLog database won't log any parameter values at input/output - this may be useful for regulatory/safety purposes, e.g. to ensure that no sensitive information (like a credit card number or a password), is logged during process - consider using TInterfaceFactory.RegisterUnsafeSpiType() instead if you prefer a more tuned filtering, for specific high-level types
- when parameters are transmitted as JSON object, any missing parameter will be replaced by their default value, unless optErrorOnMissingParam is defined to reject the call
- by default, it wil check for the client user agent, and use extended JSON if none is found (e.g. from WebSockets), or if it contains 'mORMot': you can set optForceStandardJson to ensure standard JSON is always returned
- optDontStoreVoidJson will reduce the JSON object verbosity by not writing void (e.g. 0 or '') properties when serializing objects and records
- any exceptions will be propagated during execution, unless optIgnoreException is set and the exception is trapped (not to be used unless you know what you are doing)
- optFreeTimeout will enable the time check of the _Release call using TRestServer.ServiceReleaseTimeoutMicrosec delay
TInterfaceMethodParamsDocVariantKind = ( pdvArray, pdvObject, pdvObjectFixed );
How TInterfaceMethod.TInterfaceMethod method will return the generated document
- will return either a dvObject or dvArray TDocVariantData, depending on the expected returned document layout
- returned content could be "normalized" (for any set or enumerate) if Kind is pdvObjectFixed
TInterfaceMethodValueAsm = set of ( vPassedByReference, vIsQword, vIsDynArrayString, vIsInterfaceJson);
Set of low-level processing options at assembly level
- vPassedByReference is included if the parameter is passed as reference (i.e. defined as var/out, or is a record or a reference-counted type result)
- vIsQword is set for ValueType=imvInt64 over a QWord unsigned 64-bit value
- vIsDynArrayString is set for ValueType=imvDynArray of string values
- vIsInterfaceJson is set for an interface with custom JSON serializers
TInterfaceMethodValueDirection = ( imdConst, imdVar, imdOut, imdResult );
Handled kind of parameters direction for an interface-based service method
- IN, IN/OUT, OUT directions can be applied to arguments, and will be available through our JSON-serialized remote access: smdVar and smdOut kind of parameters will be returned within the "result": JSON array
- smdResult is used for a function method, to handle the returned value
TInterfaceMethodValueDirections = set of TInterfaceMethodValueDirection;
Set of parameters direction for an interface-based service method
TInterfaceMethodValueType = ( imvNone, imvSelf, imvBoolean, imvEnum, imvSet, imvInteger, imvCardinal, imvInt64, imvDouble, imvDateTime, imvCurrency, imvRawUtf8, imvString, imvRawByteString, imvWideString, imvRecord, imvVariant, imvObject, imvRawJson, imvDynArray, imvInterface );
Handled kind of parameters for an interface-based service provider method
- we do not handle all kind of variables, but provide some enhanced types handled by JsonToObject/ObjectToJson functions (smvObject) or TDynArray.LoadFromJson / TJsonWriter.AddDynArrayJson methods (smvDynArray)
- records will be serialized as Base64 string, with our RecordSave/RecordLoad low-level format by default, or as true JSON objects since Delphi 2010 or after a Rtti.RegisterFromText/TRttiJson.RegisterCustomSerializer call
- imvRawJson will transmit the raw JSON content, without serialization
TInterfaceMethodValueTypes = set of TInterfaceMethodValueType;
Set of parameters for an interface-based service provider method
TInterfaceMethodValueVar = ( imvvNone, imvvSelf, imvv64, imvvRawUtf8, imvvString, imvvWideString, imvvRecord, imvvObject, imvvDynArray, imvvInterface );
Handled kind of parameters internal variables for an interface-based method
- reference-counted variables will have their own storage
- all non referenced-counted variables are stored within some 64-bit content
- imvVariant kind of parameter will be handled as a special imvvRecord
TInterfaceMockSpyCheck = ( chkName, chkNameParams, chkNameParamsResults );
How TInterfaceMockSpy.Verify() shall generate the calls trace
TInterfaceOption = ( optByPassAuthentication, optResultAsJsonObject, optResultAsJsonObjectWithoutResult, optResultAsXMLObject, optResultAsXMLObjectIfAcceptOnlyXML, optExcludeServiceLogCustomAnswer );
Available execution options for an interface-based service provider
- mimics TServiceFactoryServer homonymous boolean properties
TInterfaceOptions = set of TInterfaceOption;
Set of execution options for an interface-based service provider
- mimics TServiceFactoryServer homonymous boolean properties
- as used by TServiceFactoryServerAbstract.SetWholeOptions()
TInterfaceResolverListEntries = array of TInterfaceResolverListEntry;
How TInterfaceResolverList store one interface/class
TInterfaceResolverObjArray = array of TInterfaceResolver;
Used to store a list of TInterfaceResolver instances
TInterfaceStubLogDynArray = array of TInterfaceStubLog;
Used to keep track of all stubbed methods calls
TInterfaceStubLogLayout = ( wName, wParams, wResults );
Every potential part of TInterfaceStubLog.AddAsText() log entry
TInterfaceStubLogLayouts = set of TInterfaceStubLogLayout;
Set the output layout of TInterfaceStubLog.AddAsText() log entry
TInterfaceStubOption = ( imoLogMethodCallsAndResults, imoFakeInstanceWontReleaseTInterfaceStub, imoRaiseExceptionIfNoRuleDefined, imoReturnErrorIfNoRuleDefined, imoMockFailsWillPassTestCase );
Diverse options available to TInterfaceStub
- by default, method execution stack is not recorded - include imoLogMethodCallsAndResults in the options to track all method calls and the returned values; note that ExpectsTrace() method will set it
- by default, TInterfaceStub will be released when the stubed/mocked interface is released - include imoFakeInstanceWontReleaseTInterfaceStub in the options to force manual memory handling of TInterfaceStubs
- by default, all interfaces will return some default values, unless imoRaiseExceptionIfNoRuleDefined or imoReturnErrorIfNoRuleDefined is included in the options
- by default, any TInterfaceMock.Fails() rule execution will notify the TSynTestCase, unless imoMockFailsWillPassTestCase which will let test pass
TInterfaceStubOptions = set of TInterfaceStubOption;
Set of options available to TInterfaceStub
TInterfaceStubRuleKind = ( isUndefined, isExecutesJson, isExecutesVariant, isRaises, isReturns, isFails );
Diverse types of stubbing / mocking rules
- isUndefined is the first, since it will be a ExpectsCount() weak rule which may be overwritten by the other real run-time rules
TInterfaceStubRuleOperator = ( ioUndefined, ioEqualTo, ioNotEqualTo, ioLessThan, ioLessThanOrEqualTo, ioGreaterThan, ioGreaterThanOrEqualTo, ioTraceMatch );
Supported comparison operators for stubbing / mocking rules
TOnFakeInstanceDestroy = procedure(aFakeID: TInterfacedObjectFakeID) of object;
Event called when destroying a TInterfaceFactory's fake instance
- this method will be run when the fake class instance is destroyed (e.g. if aInstanceCreation is sicClientDriven, to notify the server than the client life time just finished)
TOnFakeInstanceInvoke = function(const aMethod: TInterfaceMethod; const aParams: RawUtf8; aResult, aErrorMsg: PRawUtf8; aFakeID: PInterfacedObjectFakeID; aServiceCustomAnswer: PServiceCustomAnswer): boolean of object;
Event used by TInterfaceFactory and TInterfacedObjectFake to run a method from a fake instance using JSON marshaling for the parameters
- aMethod will specify which method is to be executed
- aParams will contain the input parameters, encoded as a JSON array, without the [ ] characters (e.g. '1,"arg2",3')
- shall return TRUE on success, or FALSE in case of failure, with a corresponding explanation in aErrorMsg
- method results shall be serialized as JSON in aResult; if aServiceCustomAnswer is not nil, the result shall use this record to set HTTP custom content and headers, and ignore aResult content
- aClientDrivenID can be set optionally to specify e.g. an URI-level session
TOnInterfaceMethodExecute = procedure(Sender: TInterfaceMethodExecuteRaw; Step: TInterfaceMethodExecuteEventStep) of object;
The TInterfaceMethodExecute.OnExecute signature
- optInterceptInputOutput should be defined in Options so that Sender.Input/Output values will be filled with parameters (slower but easier to consume than Sender.Values raw pointers)
- is called for each Step, i.e. smsBefore/smsAfter
- smsError is called when TInterfaceMethodExecute.LastException was raised
TOnInterfaceMethodExecuteCallback = procedure(var Ctxt: TJsonParserContext; ParamInterfaceInfo: TRttiJson; out Obj) of object;
Callback called by TInterfaceMethodExecute to process an interface callback parameter
- implementation should set the Obj local variable to an instance of a fake class implementing the aParamInfo interface
TOnInterfaceStubExecuteJson = procedure( Ctxt: TOnInterfaceStubExecuteParamsJson) of object;
Event called by the TInterfaceStub.Executes() fluent method for JSON process
- by default Ctxt.result shall contain the default JSON array result for this method - use Ctxt.Named[] default properties, e.g. as
P := pointer(Ctxt.Params); Ctxt.Returns([GetNextItemDouble(P)-GetNextItemDouble(P)]);
- you can call Ctxt.Error() to notify the caller for an execution error
TOnInterfaceStubExecuteVariant = procedure( Ctxt: TOnInterfaceStubExecuteParamsVariant) of object;
Event called by the TInterfaceStub.Executes() fluent method for variant process
- by default Ctxt.result shall contain the default JSON array result for this method - use Ctxt.Named[] default properties, e.g. as
Ctxt['result'] := Ctxt['n1']-Ctxt['n2'];
or with Input[] / Output[] properties:
with Ctxt do Output[0] := Input[0]-Input[1];
- you can call Ctxt.Error() to notify the caller for an execution error
TOnResolverCreateInstance = procedure( Sender: TInterfaceResolver; Instance: TInterfacedObject) of object;
Event signature used by TInterfaceResolverList.OnCreateInstance
TOnServiceMethodExecuteCallback = procedure(var Par: PUtf8Char; ParamInterfaceInfo: TRttiCustom; out Obj) of object;
Callback called by TInterfaceMethodExecute to process an interface callback parameter
- implementation should set the Obj local variable to an instance of a fake class implementing the aParamInfo interface
TServiceCustomStatus = type cardinal;
An integer type to be used as result for a function method to customize the HTTP response code for interface-based services
- by default, our protocol returns HTTP_SUCCESS = 200 for any process
- using this type as result allow to return the execution error code as a regular HTTP_* response code, in addition to the regular JSON answer - i.e. there will be a "result" member in the transmitted JSON anyway
- the returned value should be in HTTP response code range, i.e. 200..599
- by design, HTTP_NOCONTENT can/should not be used: return HTTP_SUCCESS and set rsoHttp200WithNoBodyReturns204 option to let TRestServer.Uri decide and return HTTP_SUCCESS if there is an output body, or HTTP_NOCONTENT if void
TServiceInternalMethod = ( imFree, imContract, imSignature, imInstance );
Internal pseudo methods when an interface is used as remote service
- match TInterfaceFactory MethodIndex 0..3
- imFree expects an associated ClientID, other methods won't
TServiceMethodValueType = TInterfaceMethodValueType;
Backward compatibility types redirections
ARGS_IN_STACK_SIZE: array[TInterfaceMethodValueType] of cardinal = ( 0, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, 8, 8, 8, 8, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES, POINTERBYTES);
ImvNone imvSelf imvBoolean imvEnum imvSet imvInteger imvCardinal imvInt64 imvDouble imvDateTime imvCurrency imvRawUtf8 imvString imvRawByteString imvWideString imvRecord imvVariant imvObject imvRawJson imvDynArray imvInterface parameters are always aligned to 8 bytes boundaries on 64-bit ABI but may be on 32-bit or 64-bit on 32-bit CPU
ARGS_RESULT_BY_REF: TInterfaceMethodValueTypes = [imvRawUtf8, imvRawJson, imvString, imvRawByteString, imvWideString, imvRecord, imvVariant, imvDynArray];
ImvNone imvSelf imvBoolean imvEnum imvSet imvInteger imvCardinal imvInt64 imvDouble imvDateTime imvCurrency imvRawUtf8 imvString imvRawByteString imvWideString imvRecord imvVariant imvObject imvRawJson imvDynArray imvInterface
ARGS_TO_VAR: array[TInterfaceMethodValueType] of TInterfaceMethodValueVar = ( imvvNone, imvvSelf, imvv64, imvv64, imvv64, imvv64, imvv64, imvv64, imvv64, imvv64, imvv64, imvvRawUtf8, imvvString, imvvRawUtf8, imvvWideString, imvvRecord, imvvRecord, imvvObject, imvvRawUtf8, imvvDynArray, imvvInterface);
Ordinal values are stored within 64-bit buffer, and records in a RawUtf8
INTERFACEMETHOD_PERTHREADOPTIONS: array[0..3] of TInterfaceMethodOptions = ( [optExecGlobalLocked, optFreeGlobalLocked], [optExecLockedPerInterface, optFreeLockedPerInterface], [optExecInPerInterfaceThread, optFreeInPerInterfaceThread], [optExecInMainThread, optFreeInMainThread]);
The related TInterfaceMethodOptions, grouped per thread mode
INTERFACEMETHOD_THREADOPTIONS = [ optExecGlobalLocked, optFreeGlobalLocked, optExecLockedPerInterface, optFreeLockedPerInterface, optExecInPerInterfaceThread, optFreeInPerInterfaceThread, optExecInMainThread, optFreeInMainThread];
The TInterfaceMethodOptions which are related to custom thread execution
JSON_BIN_MAGIC_C = $00b2bfef;
Marker used internally to pass ServiceMethod^.ArgsInputIsOctetStream
- used by both TRestServerRoutingRest.ExecuteSoaByInterface and TInterfaceMethodExecute.ExecuteJson, followed by a RawByteString pointer
- is a UTF-8 marker, ending with a #0 so to be identified within JSON
MAX_EXECSTACK = 1024;
MAX_METHOD_ARGS = 32;
Maximum number of method arguments handled by interfaces
- if you consider this as a low value, you should better define some records/classes as DTOs instead of multiplicating parameters: so don't ask to increase this value, we rather encourage writing clean code
- used e.g. to avoid creating dynamic arrays if not needed, and ease method calls
MAX_METHOD_COUNT = 128;
Maximum number of methods handled by interfaces
- if you think this constant is too low, you are about to break the "Interface Segregation" SOLID principle: so don't ask to increase this value, we won't allow to write obviously un-SOLID code! :)
REGEAX = 1;
32-bit integer param registers (in "register" calling convention)
RESERVED_VTABLE_SLOTS = 3;
IInterface QueryInterface, _AddRef and _Release methods are hard-coded
SERVICE_PSEUDO_METHOD: array[TServiceInternalMethod] of RawUtf8 = ( '_free_', '_contract_', '_signature_', '_instance_');
URI of some pseudo methods when an interface is used as remote service
- match TInterfaceFactory MethodIndex 0..3
SERVICE_PSEUDO_METHOD_COUNT = length(SERVICE_PSEUDO_METHOD);
The number of MethodIndex which are a TServiceInternalMethod, i.e. 4
smdConst = imdConst;
TServiceMethodValueDirection = TInterfaceMethodValueDirection items
smvNone = imvNone;
TServiceMethodValueType = TInterfaceMethodValueType items
smvRecord = imvRecord;
SmvBinary = imvBinary; not defined any more (handle by RTTI itself)
smvvNone = imvvNone;
TServiceMethodValueVar = TInterfaceMethodValueVar items
VMTSTUBSIZE = 24 ;
Floating-point params are passed by reference
| Functions or procedures | Description | |
|---|---|---|
| BackgroundExecuteInstanceRelease | Low-level execution of TInterfacedObject._Release in a given background thread | |
| BackgroundExecuteThreadMethod | Low-level execution of a procedure of object in a given background thread | |
| ObjectFromInterface | Low-level function to retrieve the class instance implementing a given interface | |
| ObjectFromInterfaceImplements | Low-level function to check if a class instance, retrieved from its interface variable, does in fact implement a given interface | |
| PerThreadRunningContextAddress | Low-level internal function returning the TServiceRunningContext threadvar | |
| SetWeak | Assign a Weak interface reference, to be used for circular references | |
| SetWeakZero | Raise Internal Error C2170 on some Delphis assign a Weak interface reference, which will be ZEROed (set to nil) when the associated aObject and/or aValue will be released | |
| ToText | Returns the interface name of a registered Guid, or its hexadecimal value | |
| ToText | Return the interface execution options set as text |
procedure BackgroundExecuteInstanceRelease(instance: TObject; backgroundThread: TSynBackgroundThreadMethod);
Low-level execution of TInterfacedObject._Release in a given background thread
procedure BackgroundExecuteThreadMethod(const method: TThreadMethod; backgroundThread: TSynBackgroundThreadMethod);
Low-level execution of a procedure of object in a given background thread
function ObjectFromInterface(const aValue: IInterface): TObject;
Low-level function to retrieve the class instance implementing a given interface
- this will work with interfaces stubs generated by the compiler, but also with TInterfaceFactory.CreateFakeInstance kind of classes
- returns nil if aValue is nil or not recognized
function ObjectFromInterfaceImplements(const aValue: IInterface; const aInterface: TGuid): boolean;
Low-level function to check if a class instance, retrieved from its interface variable, does in fact implement a given interface
- this will call ObjectFromInterface(), so will work with interfaces stubs generated by the compiler, but also with TInterfaceFactory.CreateFakeInstance kind of classes
function PerThreadRunningContextAddress: pointer;
Low-level internal function returning the TServiceRunningContext threadvar
- mormot.rest.server.pas ServiceRunningContext function redirects to this
- not inlined to ensure the associated threadvar is always properly linked
procedure SetWeak(aInterfaceField: PInterface; const aValue: IInterface);
Assign a Weak interface reference, to be used for circular references
- by default setting aInterface.Field := aValue will increment the internal reference count of the implementation object: when underlying objects reference each other via interfaces (e.g. as parent and children), what causes the reference count to never reach zero, therefore resulting in memory leaks
- to avoid this issue, use this procedure instead
procedure SetWeakZero(aObject: TObject; aObjectInterfaceField: PInterface; const aValue: IInterface);
Raise Internal Error C2170 on some Delphis assign a Weak interface reference, which will be ZEROed (set to nil) when the associated aObject and/or aValue will be released
- this function is slower than SetWeak, but will avoid any GPF, by maintaining a list of per-instance weak interface field references, and hook the TObject.FreeInstance virtual method for proper zeroings
- thread-safe implementation, using per-class locked lists
function ToText(opt: TInterfaceMethodOptions): shortstring; overload;
Return the interface execution options set as text
function ToText(const aGuid: TGuid): ShortString; overload;
Returns the interface name of a registered Guid, or its hexadecimal value
GlobalInterfaceResolver: TInterfaceResolverList;
Global thread-safe list for process-wide interfaces resolution
- TInterfaceResolverInjected.RegisterGlobal/RegisterGlobalDelete class methods redirect to GlobalInterfaceResolver.Add/Delete
- initialization section of this unit will make at startup:
GlobalInterfaceResolver.Add(TypeInfo(IAutoLocker), TAutoLocker); GlobalInterfaceResolver.Add(TypeInfo(ILockedDocVariant), TLockedDocVariant);
JSONPARSER_SERVICE: TJsonParserOptions = [jpoHandleCustomVariants, jpoIgnoreUnknownEnum, jpoIgnoreUnknownProperty, jpoIgnoreStringType, jpoAllowInt64Hex, jpoNullDontReleaseObjectInstance];
Default value for TInterfaceFactory.JsonParserOptions