microsoft/teams.net

Public

mirrored from https://github.com/microsoft/teams.netAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
mehak/state

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

core/src/Microsoft.Teams.Apps/State/TurnState.cs

111lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using Microsoft.Teams.Core.Schema;
5
6namespace Microsoft.Teams.Apps.State;
7
8/// <summary>
9/// Per-turn state container exposing the conversation and user scopes. It is loaded at the start of a
10/// turn and saved when the turn completes successfully (see <see cref="TurnStateStore"/>).
11/// </summary>
12public sealed class TurnState
13{
14 internal TurnState(StateScope conversation, StateScope user, string? conversationKey = null, string? userKey = null)
15 {
16 Conversation = conversation;
17 User = user;
18 ConversationKey = conversationKey;
19 UserKey = userKey;
20 }
21
22 /// <summary>Per-conversation persisted scope.</summary>
23 public StateScope Conversation { get; }
24
25 /// <summary>Per-user persisted scope.</summary>
26 public StateScope User { get; }
27
28 /// <summary>Storage key for the conversation scope, or null when the scope is non-persisted this turn.</summary>
29 internal string? ConversationKey { get; }
30
31 /// <summary>Storage key for the user scope, or null when the scope is non-persisted this turn.</summary>
32 internal string? UserKey { get; }
33
34 /// <summary>True once the turn has completed and state has been saved; scope access then throws.</summary>
35 public bool IsCompleted { get; private set; }
36
37 /// <summary>Gets a value by path: <c>"conversation.x"</c> or <c>"user.x"</c>.</summary>
38 /// <typeparam name="T">The value type to read.</typeparam>
39 /// <param name="path">The scoped path.</param>
40 public T? GetValue<T>(string path)
41 {
42 (StateScope scope, string key) = Resolve(path);
43 return scope.Get<T>(key);
44 }
45
46 /// <summary>Sets a value by path: <c>"conversation.x"</c> or <c>"user.x"</c>.</summary>
47 /// <typeparam name="T">The value type to store.</typeparam>
48 /// <param name="path">The scoped path.</param>
49 /// <param name="value">The value to store.</param>
50 public void SetValue<T>(string path, T value)
51 {
52 (StateScope scope, string key) = Resolve(path);
53 scope.Set(key, value);
54 }
55
56 /// <summary>
57 /// Derives the storage keys for the conversation and user scopes from an inbound
58 /// <see cref="CoreActivity"/>. Including the channel id in both keys prevents state from leaking
59 /// across channels/tenants. Either key is <see langword="null"/> when the activity lacks the parts
60 /// needed to build it; that scope is then non-persisted for the turn.
61 /// </summary>
62 /// <param name="activity">The incoming activity.</param>
63 internal static (string? ConversationKey, string? UserKey) DeriveKeys(CoreActivity activity)
64 {
65 string? channelId = activity.ChannelId;
66 string? conversationId = activity.Conversation?.Id;
67 string? fromId = activity.From?.Id;
68
69 string? conversationKey = string.IsNullOrEmpty(channelId) || string.IsNullOrEmpty(conversationId)
70 ? null
71 : $"{channelId}/conversations/{conversationId}";
72
73 string? userKey = string.IsNullOrEmpty(channelId) || string.IsNullOrEmpty(fromId)
74 ? null
75 : $"{channelId}/users/{fromId}";
76
77 return (conversationKey, userKey);
78 }
79
80 internal void Complete()
81 {
82 IsCompleted = true;
83 Conversation.Complete();
84 User.Complete();
85 }
86
87 private (StateScope Scope, string Key) Resolve(string path)
88 {
89 ArgumentException.ThrowIfNullOrEmpty(path);
90
91 int dot = path.IndexOf('.', StringComparison.Ordinal);
92 if (dot < 0)
93 {
94 throw new ArgumentException(
95 $"State path '{path}' must be scope-qualified, e.g. 'conversation.{path}' or 'user.{path}'.",
96 nameof(path));
97 }
98
99 string scopeName = path[..dot];
100 string key = path[(dot + 1)..];
101 StateScope scope = scopeName switch
102 {
103 "conversation" => Conversation,
104 "user" => User,
105 _ => throw new ArgumentException(
106 $"Unknown state scope '{scopeName}' in path '{path}'. Expected 'conversation' or 'user'.",
107 nameof(path)),
108 };
109 return (scope, key);
110 }
111}
112