Quick.Options.pas 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. { ***************************************************************************
  2. Copyright (c) 2015-2021 Kike Pérez
  3. Unit : Quick.Options
  4. Description : Configuration group settings
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 18/10/2019
  8. Modified : 30/08/2021
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Options;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. Classes,
  26. RTTI,
  27. System.TypInfo,
  28. System.SysUtils,
  29. System.Generics.Collections,
  30. Quick.RTTI.Utils,
  31. Quick.Commons,
  32. Quick.FileMonitor;
  33. type
  34. Required = class(TCustomAttribute);
  35. TValidationCustomAttribute = class(TCustomAttribute)
  36. protected
  37. fErrorMsg : string;
  38. public
  39. property ErrorMsg : string read fErrorMsg write fErrorMsg;
  40. end;
  41. Range = class(TValidationCustomAttribute)
  42. private
  43. fRangeMin : Double;
  44. fRangeMax : Double;
  45. public
  46. constructor Create(aMin, aMax : Integer; const aErrorMsg : string = ''); overload;
  47. constructor Create(aMin, aMax : Double; const aErrorMsg : string = ''); overload;
  48. property Min : Double read fRangeMin write fRangeMax;
  49. property Max : Double read fRangeMax write fRangeMax;
  50. end;
  51. StringLength = class(TValidationCustomAttribute)
  52. private
  53. fMaxLength : Integer;
  54. public
  55. constructor Create(aMaxLength : Integer; const aErrorMsg : string = '');
  56. property MaxLength : Integer read fMaxLength write fMaxLength;
  57. end;
  58. TOptionsBase = class(TInterfacedObject)
  59. end;
  60. {$IFDEF DELPHIRX102_UP}
  61. TOptions = class;
  62. TConfigureOptionsProc<T : TOptions> = reference to procedure(aOptions : T);
  63. {$ELSE}
  64. TConfigureOptionsProc<T> = reference to procedure(aOptions : T);
  65. {$ENDIF}
  66. IOptionsValidator = interface
  67. ['{C6A09F78-8E34-4689-B943-83620437B9EF}']
  68. procedure ValidateOptions;
  69. end;
  70. TOptions = class(TInterfacedObject,IOptionsValidator)
  71. private
  72. fName : string;
  73. fHideOptions : Boolean;
  74. procedure DoValidateOptions; virtual;
  75. public
  76. constructor Create; virtual;
  77. property Name : string read fName write fName;
  78. property HideOptions : Boolean read fHideOptions write fHideOptions;
  79. procedure DefaultValues; virtual;
  80. {$IFDEF DELPHIRX102_UP}
  81. function ConfigureOptions<T : TOptions>(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  82. {$ELSE}
  83. function ConfigureOptions<T>(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  84. {$ENDIF}
  85. procedure ValidateOptions;
  86. end;
  87. TOptionsValidator = class(TInterfacedObject,IOptionsValidator)
  88. private
  89. fOptions : TOptions;
  90. public
  91. constructor Create(aOptions : TOptions);
  92. procedure ValidateRequired(const aInstance : TObject; aProperty: TRttiProperty);
  93. procedure ValidateStringLength(const aInstance : TObject; aProperty: TRttiProperty; aValidation : StringLength);
  94. procedure ValidateRange(const aInstance : TObject; aProperty: TRttiProperty; aValidation : Range);
  95. procedure ValidateObject(aObj : TObject);
  96. procedure ValidateArray(aValue : TValue);
  97. procedure ValidateOptions;
  98. end;
  99. TOptions<T : TOptions> = record
  100. private
  101. fOptions : T;
  102. public
  103. constructor Create(aOptions : T);
  104. function ConfigureOptions(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  105. end;
  106. IOptions<T : TOptions> = interface
  107. ['{2779F946-2692-4F74-88AD-F35F5137057A}']
  108. function GetSectionValue : T;
  109. property Value : T read GetSectionValue;
  110. end;
  111. TOptionsClass = class of TOptions;
  112. IOptionsContainer = interface
  113. ['{A593C8BB-53CF-4AA4-9641-BF974E45CBD1}']
  114. function AddSection(aOption : TOptionsClass; const aOptionsName : string = '') : TOptions;
  115. function GetOptions(aOptionClass : TOptionsClass): TOptions;
  116. function GetSection(aOptionsSection : TOptionsClass; var vOptions : TOptions) : Boolean; overload;
  117. procedure AddOption(aOption : TOptions);
  118. function ExistsSection(aOption : TOptionsClass; const aSectionName : string = '') : Boolean;
  119. end;
  120. TSectionList = TObjectList<TOptions>;
  121. IOptionsSerializer = interface
  122. ['{7DECE203-4AAE-4C9D-86C8-B3D583DF7C8B}']
  123. function Load(aSections : TSectionList; aFailOnSectionNotExists : Boolean) : Boolean;
  124. function LoadSection(aSections : TSectionList; aOptions: TOptions) : Boolean;
  125. procedure Save(aSections : TSectionList);
  126. function GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  127. function ConfigExists : Boolean;
  128. end;
  129. IFileOptionsSerializer = interface(IOptionsSerializer)
  130. ['{3417B142-2879-4DA6-86CA-19F0F427A92C}']
  131. function GetFileName : string;
  132. procedure SetFileName(const aFilename : string);
  133. property Filename : string read GetFilename write SetFilename;
  134. end;
  135. TOptionsSerializer = class(TInterfacedObject,IOptionsSerializer)
  136. public
  137. function Load(aSections : TSectionList; aFailOnSectionNotExists : Boolean) : Boolean; virtual; abstract;
  138. function LoadSection(aSections : TSectionList; aOptions: TOptions) : Boolean; virtual; abstract;
  139. procedure Save(aSections : TSectionList); virtual; abstract;
  140. function GetFileSectionNames(out oSections : TArray<string>) : Boolean; virtual; abstract;
  141. function ConfigExists : Boolean; virtual; abstract;
  142. end;
  143. TOptionsFileSerializer = class(TOptionsSerializer,IFileOptionsSerializer)
  144. private
  145. fFilename : string;
  146. function GetFileName : string;
  147. procedure SetFileName(const aFilename : string);
  148. public
  149. property Filename : string read GetFilename write SetFilename;
  150. end;
  151. TFileModifiedEvent = reference to procedure;
  152. TLoadConfigEvent = reference to procedure;
  153. TOptionValue<T : TOptions> = class(TInterfacedObject,IOptions<T>)
  154. private
  155. fValue : T;
  156. function GetSectionValue : T;
  157. public
  158. constructor Create(aValue : T);
  159. property Value : T read GetSectionValue;
  160. end;
  161. TOptionsContainer = class(TInterfacedObject,IOptionsContainer)
  162. private
  163. fSerializer : IOptionsSerializer;
  164. fSections : TSectionList;
  165. fLoaded : Boolean;
  166. fOnConfigLoaded : TLoadConfigEvent;
  167. fOnConfigReloaded : TLoadConfigEvent;
  168. function GetOptions(aOptionClass : TOptionsClass): TOptions; overload;
  169. function GetOptions(aIndex : Integer) : TOptions; overload;
  170. function GetSection(aOptionsSection : TOptionsClass; var vOptions : TOptions) : Boolean; overload;
  171. public
  172. constructor Create(aOptionsSerializer : IOptionsSerializer);
  173. destructor Destroy; override;
  174. property IsLoaded : Boolean read fLoaded;
  175. function ExistsSection(aOption : TOptionsClass; const aSectionName : string = '') : Boolean; overload;
  176. function ExistsSection<T : TOptions>(const aSectionName : string = '') : Boolean; overload;
  177. property Items[aOptionClass : TOptionsClass] : TOptions read GetOptions; default;
  178. property Items[aIndex : Integer] : TOptions read GetOptions; default;
  179. property OnConfigLoaded : TLoadConfigEvent read fOnConfigLoaded write fOnConfigLoaded;
  180. property OnConfigReloaded : TLoadConfigEvent read fOnConfigReloaded write fOnConfigReloaded;
  181. function AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions; overload;
  182. function AddSection<T : TOptions>(const aSectionName : string = '') : TOptions<T>; overload;
  183. procedure AddOption(aOption : TOptions);
  184. function GetSectionInterface<T : TOptions> : IOptions<T>;
  185. function GetSection<T : TOptions>(const aSectionName : string = '') : T; overload;
  186. function GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  187. function Count : Integer;
  188. procedure Load(aFailOnSectionNotExists : Boolean = False); virtual;
  189. procedure LoadSection(aOptions : TOptions);
  190. procedure Save; virtual;
  191. end;
  192. TFileOptionsContainer = class(TOptionsContainer)
  193. private
  194. fFilename : string;
  195. fFileMonitor : TFileMonitor;
  196. fOnFileModified : TFileModifiedEvent;
  197. fReloadIfFileChanged : Boolean;
  198. procedure CreateFileMonitor;
  199. procedure FileModifiedNotify(MonitorNotify : TMonitorNotify);
  200. procedure SetReloadIfFileChanged(const Value: Boolean);
  201. function GetFileSectionNames(out oSections: TArray<string>): Boolean;
  202. public
  203. constructor Create(aOptionsSerializer : IFileOptionsSerializer; aReloadIfFileChanged : Boolean = False);
  204. destructor Destroy; override;
  205. property FileName : string read fFilename;
  206. property ReloadIfFileChanged : Boolean read fReloadIfFileChanged write SetReloadIfFileChanged;
  207. property OnFileModified : TFileModifiedEvent read fOnFileModified write fOnFileModified;
  208. procedure Save; override;
  209. end;
  210. IOptionsBuilder<T : TOptions> = interface
  211. ['{1A1DC9A9-7F2D-4CC4-A772-6C7DBAB34424}']
  212. function Options : T;
  213. end;
  214. TOptionsBuilder<T : TOptions> = class(TInterfacedObject,IOptionsBuilder<T>)
  215. protected
  216. fOptions : T;
  217. public
  218. constructor Create;
  219. function Options : T;
  220. end;
  221. EOptionConfigureError = class(Exception);
  222. EOptionLoadError = class(Exception);
  223. EOptionSaveError = class(Exception);
  224. EOptionValidationError = class(Exception);
  225. implementation
  226. { TCustomOptionsContainer}
  227. function TOptionsContainer.ExistsSection(aOption: TOptionsClass;const aSectionName: string): Boolean;
  228. var
  229. option : TOptions;
  230. begin
  231. Result := False;
  232. for option in fSections do
  233. begin
  234. if CompareText(option.ClassName,aOption.ClassName) = 0 then
  235. begin
  236. if (not aSectionName.IsEmpty) and (CompareText(option.Name,aSectionName) = 0) then Exit(True);
  237. end;
  238. end;
  239. end;
  240. function TOptionsContainer.ExistsSection<T>(const aSectionName: string): Boolean;
  241. begin
  242. Result := GetSection<T>(aSectionName) <> nil;
  243. end;
  244. procedure TOptionsContainer.AddOption(aOption: TOptions);
  245. begin
  246. if aOption.Name.IsEmpty then aOption.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length);
  247. fSections.Add(aOption);
  248. end;
  249. function TOptionsContainer.AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions;
  250. var
  251. option : TOptions;
  252. begin
  253. //if section already exists, returns it
  254. option := Self.GetOptions(aOption);
  255. if option <> nil then Exit(option);
  256. option := aOption.Create;
  257. if aSectionName.IsEmpty then option.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length)
  258. else option.Name := aSectionName;
  259. fSections.Add(option);
  260. Result := option;
  261. end;
  262. function TOptionsContainer.AddSection<T>(const aSectionName: string): TOptions<T>;
  263. var
  264. option : TOptions;
  265. begin
  266. //if section already exists, returns it
  267. option := Self.GetSection<T>(aSectionName);
  268. if option <> nil then Exit(TOptions<T>(option));
  269. //new section
  270. option := TRTTI.CreateInstance<T>;
  271. if aSectionName.IsEmpty then option.Name := Copy(T.ClassName,2,T.ClassName.Length)
  272. else option.Name := aSectionName;
  273. fSections.Add(option);
  274. Result.Create(option);
  275. end;
  276. function TOptionsContainer.Count: Integer;
  277. begin
  278. Result := fSections.Count;
  279. end;
  280. constructor TOptionsContainer.Create(aOptionsSerializer: IOptionsSerializer);
  281. begin
  282. fSerializer := aOptionsSerializer;
  283. fSections := TSectionList.Create(False);
  284. fLoaded := False;
  285. end;
  286. destructor TOptionsContainer.Destroy;
  287. var
  288. option : TOptions;
  289. begin
  290. fSerializer := nil;
  291. for option in fSections do
  292. begin
  293. if option.RefCount = 0 then option.Free;
  294. end;
  295. fSections.Free;
  296. inherited;
  297. end;
  298. function TOptionsContainer.GetFileSectionNames(out oSections: TArray<string>): Boolean;
  299. begin
  300. end;
  301. function TOptionsContainer.GetOptions(aIndex: Integer): TOptions;
  302. begin
  303. Result := fSections[aIndex];
  304. end;
  305. function TOptionsContainer.GetSection(aOptionsSection: TOptionsClass; var vOptions: TOptions): Boolean;
  306. var
  307. option : TOptions;
  308. begin
  309. Result := False;
  310. for option in fSections do
  311. begin
  312. if option is TOptionsClass then
  313. begin
  314. vOptions := option as TOptionsClass;
  315. Exit;
  316. end;
  317. end;
  318. end;
  319. function TOptionsContainer.GetOptions(aOptionClass : TOptionsClass) : TOptions;
  320. var
  321. option : TOptions;
  322. begin
  323. Result := nil;
  324. for option in fSections do
  325. begin
  326. if option is aOptionClass then Result := option as TOptionsClass;
  327. end;
  328. end;
  329. function TOptionsContainer.GetSection<T>(const aSectionName : string = '') : T;
  330. var
  331. option : TOptions;
  332. begin
  333. Result := nil;
  334. for option in fSections do
  335. begin
  336. if option is T then
  337. begin
  338. if (aSectionName.IsEmpty) or (CompareText(option.Name,aSectionName) = 0) then
  339. begin
  340. Result := option as T;
  341. Exit;
  342. end;
  343. end;
  344. end;
  345. end;
  346. function TOptionsContainer.GetSectionInterface<T>: IOptions<T>;
  347. begin
  348. Result := TOptionValue<T>.Create(Self.GetSection<T>);
  349. end;
  350. procedure TOptionsContainer.Load(aFailOnSectionNotExists : Boolean = False);
  351. var
  352. option : TOptions;
  353. begin
  354. if fSerializer.ConfigExists then
  355. begin
  356. if not fSerializer.Load(fSections,aFailOnSectionNotExists) then Save;
  357. if not fLoaded then
  358. begin
  359. fLoaded := True;
  360. if Assigned(fOnConfigLoaded) then fOnConfigLoaded;
  361. end
  362. else if Assigned(fOnConfigReloaded) then fOnConfigReloaded;
  363. end
  364. else
  365. begin
  366. //if not exists file get default values
  367. for option in fSections do option.DefaultValues;
  368. //creates default file
  369. Save;
  370. end;
  371. end;
  372. procedure TOptionsContainer.LoadSection(aOptions : TOptions);
  373. begin
  374. if fSerializer.ConfigExists then
  375. begin
  376. if not fSerializer.LoadSection(fSections,aOptions) then Save;
  377. end;
  378. end;
  379. procedure TOptionsContainer.Save;
  380. begin
  381. fSerializer.Save(fSections);
  382. end;
  383. { TOptionsContainer }
  384. constructor TFileOptionsContainer.Create(aOptionsSerializer : IFileOptionsSerializer; aReloadIfFileChanged : Boolean = False);
  385. begin
  386. inherited Create(aOptionsSerializer);
  387. fFilename := aOptionsSerializer.Filename;
  388. if aReloadIfFileChanged then CreateFileMonitor;
  389. end;
  390. procedure TFileOptionsContainer.Save;
  391. var
  392. laststate : Boolean;
  393. begin
  394. //disable filemonitor to avoid detect manual save as a external file change
  395. if fReloadIfFileChanged then
  396. begin
  397. laststate := fFileMonitor.Enabled;
  398. fFileMonitor.Enabled := False;
  399. try
  400. //save config file
  401. inherited;
  402. finally
  403. //set last state
  404. Sleep(0);
  405. fFileMonitor.Enabled := laststate;
  406. end;
  407. end
  408. else inherited;
  409. end;
  410. procedure TFileOptionsContainer.SetReloadIfFileChanged(const Value: Boolean);
  411. begin
  412. if Value = fReloadIfFileChanged then Exit;
  413. fReloadIfFileChanged := Value;
  414. if Assigned(fFileMonitor) then fFileMonitor.Free;
  415. if fReloadIfFileChanged then CreateFileMonitor;
  416. end;
  417. procedure TFileOptionsContainer.CreateFileMonitor;
  418. begin
  419. fFileMonitor := TQuickFileMonitor.Create;
  420. fFileMonitor.FileName := fFilename;
  421. fFileMonitor.Interval := 2000;
  422. fFileMonitor.Notifies := [TMonitorNotify.mnFileModified];
  423. fFileMonitor.OnFileChange := FileModifiedNotify;
  424. fFileMonitor.Enabled := True;
  425. end;
  426. destructor TFileOptionsContainer.Destroy;
  427. begin
  428. if Assigned(fFileMonitor) then fFileMonitor.Free;
  429. inherited;
  430. end;
  431. procedure TFileOptionsContainer.FileModifiedNotify(MonitorNotify: TMonitorNotify);
  432. begin
  433. if MonitorNotify = TMonitorNotify.mnFileModified then
  434. begin
  435. if Assigned(fOnFileModified) then fOnFileModified;
  436. if fReloadIfFileChanged then
  437. begin
  438. Load(False);
  439. end;
  440. end;
  441. end;
  442. function TFileOptionsContainer.GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  443. begin
  444. Result := fSerializer.GetFileSectionNames(oSections);
  445. end;
  446. { TOptions }
  447. function TOptions.ConfigureOptions<T>(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
  448. var
  449. value : TValue;
  450. begin
  451. Result := TOptionsValidator.Create(Self);
  452. if Assigned(aOptionsFunc) then
  453. begin
  454. value := Self;
  455. aOptionsFunc(value.AsType<T>);
  456. end;
  457. end;
  458. constructor TOptions.Create;
  459. begin
  460. fName := '';
  461. fHideOptions := False;
  462. end;
  463. procedure TOptions.DefaultValues;
  464. begin
  465. //nothing
  466. end;
  467. procedure TOptions.DoValidateOptions;
  468. var
  469. ivalidator : IOptionsValidator;
  470. begin
  471. ivalidator := TOptionsValidator.Create(Self);
  472. ivalidator.ValidateOptions;
  473. end;
  474. procedure TOptions.ValidateOptions;
  475. begin
  476. try
  477. DoValidateOptions;
  478. except
  479. on E : Exception do
  480. begin
  481. raise EOptionConfigureError.CreateFmt('Validation Options Error : %s',[e.Message]);
  482. end;
  483. end;
  484. end;
  485. { TOptionsValidator }
  486. procedure TOptionsValidator.ValidateObject(aObj : TObject);
  487. var
  488. ctx : TRttiContext;
  489. rtype : TRttiType;
  490. rprop : TRttiProperty;
  491. attrib : TCustomAttribute;
  492. rvalue : TValue;
  493. begin
  494. rtype := ctx.GetType(aObj.ClassInfo);
  495. for rprop in rtype.GetProperties do
  496. begin
  497. //check only published properties
  498. if rprop.Visibility = TMemberVisibility.mvPublished then
  499. begin
  500. //check validation option attributes
  501. for attrib in rprop.GetAttributes do
  502. begin
  503. if attrib is Required then ValidateRequired(aObj,rprop)
  504. else if attrib is StringLength then ValidateStringLength(aObj,rprop,StringLength(attrib))
  505. else if attrib is Range then ValidateRange(aObj,rprop,Range(attrib));
  506. end;
  507. rvalue := rprop.GetValue(aObj);
  508. if not rvalue.IsEmpty then
  509. begin
  510. case rvalue.Kind of
  511. tkClass : ValidateObject(rvalue.AsObject);
  512. tkDynArray : ValidateArray(rvalue);
  513. end;
  514. end;
  515. end;
  516. end;
  517. end;
  518. constructor TOptionsValidator.Create(aOptions: TOptions);
  519. begin
  520. fOptions := aOptions;
  521. end;
  522. procedure TOptionsValidator.ValidateOptions;
  523. begin
  524. ValidateObject(fOptions);
  525. end;
  526. procedure TOptionsValidator.ValidateArray(aValue : TValue);
  527. type
  528. PPByte = ^PByte;
  529. var
  530. ctx : TRttiContext;
  531. rDynArray : TRttiDynamicArrayType;
  532. itvalue : TValue;
  533. i : Integer;
  534. begin
  535. rDynArray := ctx.GetType(aValue.TypeInfo) as TRTTIDynamicArrayType;
  536. for i := 0 to aValue.GetArrayLength - 1 do
  537. begin
  538. TValue.Make(PPByte(aValue.GetReferenceToRawData)^ + rDynArray.ElementType.TypeSize * i, rDynArray.ElementType.Handle,itvalue);
  539. if not itvalue.IsEmpty then
  540. begin
  541. case itvalue.Kind of
  542. tkClass : ValidateObject(itvalue.AsObject);
  543. tkDynArray : ValidateArray(itvalue);
  544. end;
  545. end;
  546. end;
  547. end;
  548. procedure TOptionsValidator.ValidateRange(const aInstance : TObject; aProperty: TRttiProperty; aValidation : Range);
  549. var
  550. value : TValue;
  551. msg : string;
  552. begin
  553. value := aProperty.GetValue(aInstance);
  554. if not value.IsEmpty then
  555. begin
  556. if value.Kind = tkFloat then
  557. begin
  558. if (value.AsExtended < aValidation.Min) or (value.AsExtended > aValidation.Max) then
  559. begin
  560. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds predefined range (%2f - %2f)',[fOptions.Name,aInstance.ClassName,aProperty.Name,aValidation.Min,aValidation.Max])
  561. else msg := aValidation.ErrorMsg;
  562. raise EOptionValidationError.Create(msg);
  563. end;
  564. end
  565. else if value.Kind in [tkInteger,tkInt64] then
  566. begin
  567. if (value.AsInt64 < aValidation.Min) or (value.AsInt64 > aValidation.Max) then
  568. begin
  569. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds predefined range (%d - %d)',[fOptions.Name,aInstance.ClassName,aProperty.Name,Round(aValidation.Min),Round(aValidation.Max)])
  570. else msg := aValidation.ErrorMsg;
  571. raise EOptionValidationError.Create(msg);
  572. end;
  573. end;
  574. end;
  575. end;
  576. procedure TOptionsValidator.ValidateRequired(const aInstance : TObject; aProperty: TRttiProperty);
  577. begin
  578. if aProperty.GetValue(aInstance).IsEmpty then raise EOptionValidationError.CreateFmt('Option %s "%s.%s" is required',[fOptions.Name,aInstance.ClassName,aProperty.Name]);
  579. end;
  580. procedure TOptionsValidator.ValidateStringLength(const aInstance : TObject; aProperty: TRttiProperty; aValidation : StringLength);
  581. var
  582. value : TValue;
  583. msg : string;
  584. begin
  585. value := aProperty.GetValue(aInstance);
  586. if (not value.IsEmpty) and (value.AsString.Length > aValidation.MaxLength) then
  587. begin
  588. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds max length (%d)',[fOptions.Name,aInstance.ClassName,aProperty.Name,aValidation.MaxLength])
  589. else msg := aValidation.ErrorMsg;
  590. raise EOptionValidationError.Create(msg);
  591. end;
  592. end;
  593. { Range }
  594. constructor Range.Create(aMin, aMax: Integer; const aErrorMsg : string = '');
  595. begin
  596. fRangeMin := aMin;
  597. fRangeMax := aMax;
  598. fErrorMsg := aErrorMsg;
  599. end;
  600. constructor Range.Create(aMin, aMax: Double; const aErrorMsg: string);
  601. begin
  602. fRangeMin := aMin;
  603. fRangeMax := aMax;
  604. fErrorMsg := aErrorMsg;
  605. end;
  606. { StringLength }
  607. constructor StringLength.Create(aMaxLength: Integer; const aErrorMsg : string = '');
  608. begin
  609. fMaxLength := aMaxLength;
  610. fErrorMsg := aErrorMsg;
  611. end;
  612. { TOptionValue<T> }
  613. constructor TOptionValue<T>.Create(aValue: T);
  614. begin
  615. fValue := aValue;
  616. end;
  617. function TOptionValue<T>.GetSectionValue: T;
  618. begin
  619. Result := fValue;
  620. end;
  621. { TOptions<T> }
  622. function TOptions<T>.ConfigureOptions(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
  623. begin
  624. if Assigned(aOptionsFunc) then Result := fOptions.ConfigureOptions<T>(aOptionsFunc)
  625. else Result := TOptionsValidator.Create(fOptions);
  626. end;
  627. constructor TOptions<T>.Create(aOptions: T);
  628. begin
  629. fOptions := aOptions;
  630. end;
  631. { TOptionsBuilder<T> }
  632. constructor TOptionsBuilder<T>.Create;
  633. begin
  634. fOptions := (PTypeInfo(TypeInfo(T)).TypeData.ClassType.Create) as T;
  635. end;
  636. function TOptionsBuilder<T>.Options: T;
  637. begin
  638. Result := fOptions;
  639. end;
  640. { TOptionsFileSerializer }
  641. function TOptionsFileSerializer.GetFileName: string;
  642. begin
  643. Result := fFilename;
  644. end;
  645. procedure TOptionsFileSerializer.SetFileName(const aFilename: string);
  646. begin
  647. fFilename := aFilename;
  648. end;
  649. end.