My favorites | Sign in
Logo
Project hosting will be READ-ONLY Wednesday, 7AM PST due to brief network maintenance
             
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
#region (c)2008 Lokad - New BSD license

// Copyright (c) Lokad 2008
// Company: http://www.lokad.com
// This code is released under the terms of the new BSD licence

#endregion

using System;
using System.Linq;
using Lokad.Rules;

namespace Lokad.Api
{
/// <summary> Rules for the Lokad APIv2. See http://lokad.svn.sourceforge.net/viewvc/lokad/Platform/Trunk/Shared/Source/Lokad.Api.Core/ApiRules.cs?view=markup</summary>
public static class ApiRules
{
/// <summary>
/// Characters that are invalid in the names of tags, events, series etc: <em>;[]&lt;&gt;\/</em>
/// </summary>
public static readonly char[] IllegalCharacters = ";[]<>\"/".ToCharArray();


/// <summary>
/// Name can't have IllegalCharacters and must be valid for XMLSerialization.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="scope">The scope.</param>
internal static void ValidName(string name, IScope scope)
{
scope.ValidateInScope(name,
StringIs.Without(IllegalCharacters),
StringIs.ValidForXmlSerialization);
}

/// <summary>
/// Name of the tag must be comply with <see cref="ValidName"/> and should have length within 1 and 32 characters.
/// </summary>
public static void ValidTag(string tag, IScope scope)
{
scope.ValidateInScope(tag, StringIs.Limited(1, 32), ValidName);
}

/// <summary>
/// User name should have length within 6 and 256 characters and should be a valid email.
/// </summary>
internal static readonly Rule<string>[] UserName = new[]
{
StringIs.Limited(6, 256),
StringIs.ValidEmail
};

/// <summary>
/// Password should have length within 1 and 256 characters
/// </summary>
static readonly Rule<string> Password = StringIs.Limited(1, 256);

/// <summary> Report should have a valid Subject (length within 5 and 256 characters)
/// and a valid Message (length within 2 and 4000 characters)</summary>
public static void Report(Report report, IScope scope)
{
scope.Validate(report.Subject, "Subject", StringIs.Limited(5,256));
scope.Validate(report.Message, "Message", StringIs.Limited(2, 4000));
scope.Validate(report.Information, "Information");
}

/// <summary> Idenity should have Username according to the
/// <see cref="UserName"/> rule and password that is valid within the
/// <see cref="Password"/>rule.</summary>
/// <param name="identity">The identity.</param>
/// <param name="scope">The scope.</param>
public static void Identity(Identity identity, IScope scope)
{
scope.Validate(identity.Username, "Username", UserName);
scope.Validate(identity.Password, "Password", Password);
}

/// <summary>
/// Task should have <see cref="TaskInfo.Period"/> and
/// <see cref="TaskInfo.SerieID"/> with set values. <see cref="TaskInfo.PeriodStart"/> must pass <see cref="ValidDate"/>.
/// <see cref="TaskInfo.FuturePeriods"/> should be within a valid range as defined by <see cref="ValidateFuturePeriods"/>.
/// </summary>
/// <param name="info">The info.</param>
/// <param name="scope">The scope.</param>
static void TaskBody(TaskInfo info, IScope scope)
{
scope.Validate(info.Period, "Period", Is.NotDefault);
scope.Validate(info.SerieID, "SerieID", Is.NotDefault);

if (info.PeriodStart != DateTime.MinValue)
{
scope.Validate(info.PeriodStart, "PeriodStart", ValidDate);
if (info.PeriodStart.Day > 28)
scope.Error("PeriodStart must have day smaller than or equal to 28");
}

scope.Validate(info.FuturePeriods, "FuturePeriods", ValidateFuturePeriods(info.Period));
}


/// <summary> <see cref="TaskInfo.FuturePeriods"/> should be greater than 0 and less
/// than 64 for all periods, with the following exceptions:
/// <see cref="Period.QuarterHour"/> - 6*7*24,
/// <see cref="Period.HalfHour"/> - 6*7*24*2,
/// <see cref="Period.Hour"/> - 6*7*24.
/// </summary>
/// <param name="period">The period.</param>
/// <returns>new rule instance</returns>
public static Rule<int> ValidateFuturePeriods(Period period)
{
var max = GetMaxFuturePeriods(period);
return (i, scope) => scope.ValidateInScope(i, Is.Within(1, max));
}


static int GetMaxFuturePeriods(Period period)
{
switch (period)
{
case Period.QuarterHour:
return 6*7*24;
case Period.HalfHour:
return 6*7*24*2;
case Period.Hour:
return 6*7*24;
default:
return 64;
}
}

/// <summary>
/// New task should have <see cref="TaskInfo.TaskID"/> that is not set and
/// should comply with the <see cref="TaskBody"/> rule.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="scope">The scope.</param>
internal static void NewTask(TaskInfo task, IScope scope)
{
scope.Validate(task.TaskID, "TaskID", Is.Default);
scope.ValidateInScope(task, TaskBody);
}

/// <summary>
/// Existing task should have <see cref="TaskInfo.TaskID"/> that is set
/// to a valid value and should comply with the <see cref="TaskBody"/> rule.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="scope">The scope.</param>
internal static void Task(TaskInfo task, IScope scope)
{
scope.Validate(task.TaskID, "TaskID", Is.NotDefault);
scope.ValidateInScope(task, TaskBody);
}

/// <summary>
/// Name of a serie must comply with <see cref="ValidName"/> rule and
/// have lentgth within 1 and 64 characters.
/// </summary>
internal static readonly Rule<string>[] SerieName = new[]
{
StringIs.Limited(1, 64), ValidName
};

/// <summary>
/// New serie should have <see cref="SerieInfo.SerieID"/> that is not set
/// and <see cref="SerieInfo.Name"/> that passes <see cref="SerieName"/> rule.
/// </summary>
/// <param name="serieInfo">The serie info.</param>
/// <param name="scope">The scope.</param>
internal static void NewSerie(SerieInfo serieInfo, IScope scope)
{
scope.Validate(serieInfo.SerieID, "SerieID", Is.Default);
scope.Validate(serieInfo.Name, "Name", SerieName);
}

/// <summary>
/// Segments array should have legth limited by <see cref="LokadApiRequestLimits.UpdateSerieSegments_Segments"/>
/// while the total value should be less than <see cref="LokadApiRequestLimits.UpdateSerieSegments_Values"/>.
/// Each segment should comply with the <see cref="Segment"/> rule and have SerieId that is unique within this call.
/// </summary>
/// <param name="segments">The segments.</param>
/// <param name="scope">The scope.</param>
internal static void Segments(SegmentForSerie[] segments, IScope scope)
{
const int sLim = LokadApiRequestLimits.UpdateSerieSegments_Segments;
if (segments.Length > sLim)
{
scope.Error("Only {0} segments are allowed per request", sLim);
}

var duplicateSerieIds = segments
.GroupBy(s => s.SerieID)
.Count(g => g.Count() > 1);

if (duplicateSerieIds > 0)
{
scope.Error("SerieIds must be unique within the call");
}

var total = segments.Sum(s => s.Values.Length);
const int vLim = LokadApiRequestLimits.UpdateSerieSegments_Values;
if (total > vLim)
{
scope.Error("Only {0} values are allowed per request.", vLim);
}
else
{
scope.ValidateInScope(segments, Segment);
}
}

/// <summary>
/// Segment should have <see cref="SegmentForSerie.SerieID"/> set to a
/// valid identifier. Each item within <see cref="SegmentForSerie.Values"/>
/// should pass <see cref="Value"/> rule.
/// </summary>
/// <param name="segment">The segment.</param>
/// <param name="scope">The scope.</param>
static void Segment(SegmentForSerie segment, IScope scope)
{
scope.Validate(segment.SerieID, "SerieID", Is.NotDefault);
scope.ValidateMany(segment.Values, "Values", Value);
}

/// <summary>
/// <see cref="TimeValue.Value"/> should represent a valid double, while
/// <see cref="TimeValue.Time"/> should pass the <see cref="ValidDate"/> rule.
/// </summary>
/// <param name="timeValue">The time value.</param>
/// <param name="scope">The scope.</param>
static void Value(TimeValue timeValue, IScope scope)
{
scope.Validate(timeValue.Time, "Time", ValidDate);
scope.Validate(timeValue.Value, "Value", DoubleIs.Valid);
}

/// <summary>
/// While setting tags, array of <see cref="TagsForSerie"/> should have length limited by <see cref="LokadApiRequestLimits.SetTags_Series"/>,
/// while the totak number of tags should be less than <see cref="LokadApiRequestLimits.SetTags_TagsPerRequest"/>.
/// Every item in the array should be valid according to the <see cref="Tags"/> rule.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="scope">The scope.</param>
internal static void SetTags(TagsForSerie[] tags, IScope scope)
{
if (tags.Length > LokadApiRequestLimits.SetTags_Series)
{
scope.Error("Only {0} items are allowed per request", LokadApiRequestLimits.SetTags_Series);
}

var total = tags.Sum(t => t.Tags.Length);
if (total > LokadApiRequestLimits.SetTags_TagsPerRequest)
{
scope.Error("Only {0} tags are allowed per request", LokadApiRequestLimits.SetTags_TagsPerRequest);
}
else
{
scope.ValidateInScope(tags, Tags);
}
}



/// <summary>
/// <see cref="TagsForSerie.SerieID"/> should be set to a valid value, while
/// every tag in the array should comply with the <see cref="ValidTag"/> rule.
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="scope">The scope.</param>
static void Tags(TagsForSerie tags, IScope scope)
{
scope.Validate(tags.SerieID, "SerieID", Is.NotDefault);
scope.ValidateMany(tags.Tags, "Tags", ValidTag);
}

/// <summary>
/// While setting the events, number of items in the array should be less than
/// <see cref="LokadApiRequestLimits.SetEvents_Series"/>, while the total number of events should be
/// less than <see cref="LokadApiRequestLimits.SetEvents_EventsPerRequest"/>. Each array item should
/// comply with the <see cref="SetEvents"/> rule.
/// </summary>
/// <param name="events">The events.</param>
/// <param name="scope">The scope.</param>
internal static void SetEvents(EventsForSerie[] events, IScope scope)
{
if (events.Length > LokadApiRequestLimits.SetEvents_Series)
{
scope.Error("Only {0} items are allowed per request", LokadApiRequestLimits.SetEvents_Series);
}
var total = events.Sum(s => s.Events.Length);
if (total > LokadApiRequestLimits.SetEvents_EventsPerRequest)
{
scope.Error("Only {0} events are allowed per request", LokadApiRequestLimits.SetEvents_EventsPerRequest);
}
else
{
scope.ValidateInScope(events, Events);
}
}

/// <summary>
/// <see cref="EventsForSerie.SerieID"/> should be set to a valid value,
/// while every item in the array should comply with the <see cref="Event"/> rule.
/// </summary>
/// <param name="serieEvent">The serie event.</param>
/// <param name="scope">The scope.</param>
static void Events(EventsForSerie serieEvent, IScope scope)
{
scope.Validate(serieEvent.SerieID, "SerieID", Is.NotDefault);
scope.ValidateMany(serieEvent.Events, "Events", Event);
}

/// <summary>
/// <see cref="SerieEvent.Name"/> should have length within 1 and 32 characters
/// and comply with the <see cref="ValidName"/>. <see cref="SerieEvent.Time"/> should pass
/// <see cref="ValidDate"/> rule. <see cref="SerieEvent.DurationDays"/> should be a non-negative valid
/// double that is smaller than 10^6. <see cref="SerieEvent.KnownSince"/> should either be empty
/// or pass <see cref="ValidDate"/> rule.
/// </summary>
/// <param name="e">The event to validate.</param>
/// <param name="scope">The scope to capture results.</param>
public static void Event(SerieEvent e, IScope scope)
{
scope.Validate(e.Name, "Name", StringIs.Limited(1, 32), ValidName);
scope.Validate(e.Time, "Time", ValidDate);
scope.Validate(e.DurationDays, "DurationDays", Is.Within(0d, TimeSpan.MaxValue.TotalDays), DoubleIs.Valid);

if (e.KnownSince != DateTime.MinValue)
scope.Validate(e.KnownSince, "KnownSince", ValidDate);
}

/// <summary>
/// All dates should represent a valid MS SQL date. They must be represented in culture invariant format
/// and be greater than 1753-01-01.
/// </summary>
static readonly Rule<DateTime> ValidDate = DateIs.SqlCompatible;


/// <summary>
/// Each connection should have <see cref="ServiceConnection.Username"/> passing the <see cref="UserName"/> rule,
/// while <see cref="ServiceConnection.Password"/> passes the <see cref="Password"/> rule and an
/// <see cref="ServiceConnection.Endpoint"/> is valid according to the <see cref="Endpoint"/> rule.
/// </summary>
/// <param name="connection">The connection.</param>
/// <param name="scope">The scope.</param>
public static void ValidConnection(ServiceConnection connection, IScope scope)
{
scope.Validate(connection.Username, "Username", UserName);
scope.Validate(connection.Password, "Password", Password);
scope.Validate(connection.Endpoint, "Endpoint", Endpoint);
}

/// <summary>
/// Endpoint should be hosted on Lokad servers and represent a valid connection string.
/// </summary>
/// <param name="serverUrl">The server URL.</param>
/// <param name="scope">The scope.</param>
static void Endpoint(Uri serverUrl, IScope scope)
{
var local = serverUrl.LocalPath.ToLowerInvariant();
switch (local)
{
case "/timeseries.asmx":
case "/forecasting.asmx":
scope.Error("Please, use forecasting2.asmx");
break;
case "/timeseries2.asmx":
case "/forecasting2.asmx":
case "/forecasting2.svc":
break;
default:
scope.Error("Unsupported local address '{0}'", local);
break;
}

if (!serverUrl.IsLoopback)
{
var host = serverUrl.Host.ToLowerInvariant();
if ((host != "ws.lokad.com") && (host != "sandbox-ws.lokad.com"))
scope.Warn("This host is not supported by Lokad: '{0}'", host);
}
}
}
}
Show details Hide details

Change log

r578 by rinat.abdullin on Nov 23, 2009   Diff
Api: enforcing unique SerieIds in the
documentation and rule checks.
Go to: 
Project members, sign in to write a code review

Older revisions

r140 by rinat.abdullin on May 30, 2009   Diff
SDK: more descriptions on ApiRules.
r138 by rinat.abdullin on May 30, 2009   Diff
SDK: documenting the API and working
on semi-automated help generation for
the WS (to minimize manual syncs).
r129 by rinat.abdullin on May 28, 2009   Diff
SDK: fixing the WCF detection routine.
All revisions of this file

File info

Size: 14410 bytes, 384 lines
Hosted by Google Code