Quick.Options.pas 22 KB

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