xref: /openbmc/phosphor-user-manager/json_serializer.hpp (revision f21966594e9735887b3091616df643c24dd14979)
1 #pragma once
2 
3 #include <nlohmann/json.hpp>
4 #include <phosphor-logging/elog-errors.hpp>
5 #include <phosphor-logging/elog.hpp>
6 #include <phosphor-logging/lg2.hpp>
7 
8 #include <format>
9 #include <fstream>
10 #include <ranges>
11 #include <string>
12 #include <tuple>
13 #include <type_traits>
14 #include <utility>
15 
16 class JsonSerializer
17 {
18   public:
JsonSerializer(std::string path,nlohmann::json js=nlohmann::json ())19     JsonSerializer(std::string path, nlohmann::json js = nlohmann::json()) :
20         mfaConfPath(path), jsonData(std::move(js))
21     {}
22 
stringSplitter()23     inline auto stringSplitter()
24     {
25         return std::views::split('/') | std::views::transform([](auto&& sub) {
26                    return std::string(sub.begin(), sub.end());
27                });
28     }
makeJson(const std::string & key,const std::string & value)29     nlohmann::json makeJson(const std::string& key, const std::string& value)
30     {
31         auto keys = key | stringSplitter();
32         std::vector v(keys.begin(), keys.end());
33         auto rv = v | std::views::reverse;
34         nlohmann::json init;
35         init[rv.front()] = value;
36         auto newJson = std::reduce(rv.begin() + 1, rv.end(), init,
37                                    [](auto sofar, auto currentKey) {
38                                        nlohmann::json j;
39                                        j[currentKey] = sofar;
40                                        return j;
41                                    });
42         return newJson;
43     }
getLeafNode(const std::string_view keyPath)44     std::optional<nlohmann::json> getLeafNode(const std::string_view keyPath)
45     {
46         auto keys = keyPath | stringSplitter();
47         nlohmann::json current = jsonData;
48         for (auto key : keys)
49         {
50             if (!current.contains(key))
51             {
52                 return std::nullopt;
53             }
54             current = current[key];
55         }
56         return current;
57     }
serialize(std::string key,const std::string value)58     void serialize(std::string key, const std::string value)
59     {
60         jsonData.merge_patch(makeJson(key, value));
61     }
62     template <typename T>
deserialize(std::string key,T & value)63     void deserialize(std::string key, T& value)
64     {
65         auto leaf = getLeafNode(key);
66         if (leaf)
67         {
68             value = *leaf;
69         }
70     }
erase(std::string key)71     void erase(std::string key)
72     {
73         if (jsonData.contains(key))
74         {
75             jsonData.erase(key);
76         }
77     }
store()78     bool store()
79     {
80         std::filesystem::path dir =
81             std::filesystem::path(mfaConfPath).parent_path();
82 
83         // Check if the directory exists, and create it if it does not
84         if (!dir.string().empty() && !std::filesystem::exists(dir))
85         {
86             std::error_code ec;
87             if (!std::filesystem::create_directories(dir, ec))
88             {
89                 lg2::error("Unable to create directory {DIR}", "DIR",
90                            dir.string());
91                 return false;
92             }
93         }
94         std::ofstream file(mfaConfPath.data());
95         if (file.is_open())
96         {
97             file << jsonData.dump(4); // Pretty print with 4 spaces
98             file.close();
99             return true;
100         }
101         else
102         {
103             lg2::error("Unable to open file {FILENAME}", "FILENAME",
104                        mfaConfPath);
105             return false;
106         }
107     }
load()108     bool load()
109     {
110         std::ifstream file(mfaConfPath.data());
111 
112         if (file.is_open())
113         {
114             try
115             {
116                 file >> jsonData;
117                 file.close();
118                 return true;
119             }
120             catch (const nlohmann::json::parse_error& e)
121             {
122                 lg2::error("JSON parsing error: {MSG} in file {FILENAME}",
123                            "MSG", e.what(), "FILENAME", mfaConfPath);
124                 if (std::filesystem::exists(mfaConfPath))
125                 {
126                     std::filesystem::remove(mfaConfPath);
127                 }
128                 file.close();
129                 return false;
130             }
131         }
132         else
133         {
134             lg2::error("Unable to open file for reading {FILENAME}", "FILENAME",
135                        mfaConfPath);
136             return false;
137         }
138     }
139 
140   private:
141     const std::string mfaConfPath;
142     nlohmann::json jsonData;
143 };
144