| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "google_apis/calendar/calendar_api_response_types.h" |
| |
| #include <stddef.h> |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/json/json_value_converter.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "google_apis/common/parser_util.h" |
| #include "google_apis/common/time_util.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace google_apis { |
| |
| namespace calendar { |
| |
| namespace { |
| |
| // EventList |
| constexpr char kCalendarEventListKind[] = "calendar#events"; |
| constexpr char kTimeZone[] = "timeZone"; |
| |
| // DateTime |
| constexpr char kDateTime[] = "dateTime"; |
| |
| // Date |
| constexpr char kDate[] = "date"; |
| |
| // CalendarEvent |
| constexpr char kAttendees[] = "attendees"; |
| constexpr char kAttendeesOmitted[] = "attendeesOmitted"; |
| constexpr char kAttendeesResponseStatus[] = "responseStatus"; |
| constexpr char kAttendeesSelf[] = "self"; |
| constexpr char kCalendarEventKind[] = "calendar#event"; |
| constexpr char kColorId[] = "colorId"; |
| constexpr char kEnd[] = "end"; |
| constexpr char kHtmlLink[] = "htmlLink"; |
| constexpr char kPathToCreatorSelf[] = "creator.self"; |
| constexpr char kStart[] = "start"; |
| constexpr char kStatus[] = "status"; |
| constexpr char kSummary[] = "summary"; |
| |
| constexpr auto kEventStatuses = |
| base::MakeFixedFlatMap<base::StringPiece, CalendarEvent::EventStatus>( |
| {{"cancelled", CalendarEvent::EventStatus::kCancelled}, |
| {"confirmed", CalendarEvent::EventStatus::kConfirmed}, |
| {"tentative", CalendarEvent::EventStatus::kTentative}}); |
| |
| constexpr auto kAttendeesResponseStatuses = |
| base::MakeFixedFlatMap<base::StringPiece, CalendarEvent::ResponseStatus>( |
| {{"accepted", CalendarEvent::ResponseStatus::kAccepted}, |
| {"declined", CalendarEvent::ResponseStatus::kDeclined}, |
| {"needsAction", CalendarEvent::ResponseStatus::kNeedsAction}, |
| {"tentative", CalendarEvent::ResponseStatus::kTentative}}); |
| |
| // Converts the `items` field from the response. This method helps to use the |
| // custom conversion entrypoint `CalendarEvent::CreateFrom`. |
| // Returns false when it fails (e.g. the value is structurally different from |
| // expected). |
| bool ConvertResponseItems(const base::Value* value, |
| std::vector<std::unique_ptr<CalendarEvent>>* result) { |
| const auto* items = value->GetIfList(); |
| if (!items) |
| return false; |
| |
| result->reserve(items->size()); |
| for (const auto& item : *items) { |
| auto event = CalendarEvent::CreateFrom(item); |
| if (!event) |
| return false; |
| result->push_back(std::move(event)); |
| } |
| |
| return true; |
| } |
| |
| // Converts the event status to `EventStatus`. Returns false when it fails |
| // (e.g. the value is structurally different from expected). |
| bool ConvertEventStatus(const base::Value* value, |
| CalendarEvent::EventStatus* result) { |
| DCHECK(value); |
| DCHECK(result); |
| |
| const auto* status = value->GetIfString(); |
| if (!status) { |
| return false; |
| } |
| |
| const auto* it = kEventStatuses.find(*status); |
| if (it != kEventStatuses.end()) { |
| *result = it->second; |
| } else { |
| *result = CalendarEvent::EventStatus::kUnknown; |
| } |
| return true; |
| } |
| |
| // Returns user's self response status on the event, or `absl::nullopt` in case |
| // the passed value is structurally different from expected. |
| absl::optional<CalendarEvent::ResponseStatus> CalculateSelfResponseStatus( |
| const base::Value& value) { |
| const auto* event = value.GetIfDict(); |
| if (!event) |
| return absl::nullopt; |
| |
| const auto* attendees_raw_value = event->Find(kAttendees); |
| if (!attendees_raw_value) { |
| // It's okay not to have `attendees` field in the response, it's possible |
| // in two cases: |
| // - this is a personal event of the `default` type without other |
| // attendees; |
| // - user invited 2+ guests and removed themselves from the event (also, |
| // the `attendeesOmitted` flag will be set to true). |
| |
| const bool is_self_created = |
| event->FindBoolByDottedPath(kPathToCreatorSelf).value_or(false); |
| const bool has_omitted_attendees = |
| event->FindBool(kAttendeesOmitted).value_or(false); |
| |
| if (is_self_created && !has_omitted_attendees) { |
| // It's not possible to create a personal event and mark it as |
| // tentative or declined. |
| return CalendarEvent::ResponseStatus::kAccepted; |
| } |
| |
| return CalendarEvent::ResponseStatus::kUnknown; |
| } |
| |
| const auto* attendees = attendees_raw_value->GetIfList(); |
| if (!attendees) |
| return absl::nullopt; |
| |
| for (const auto& x : *attendees) { |
| const auto* attendee = x.GetIfDict(); |
| if (!attendee) |
| return absl::nullopt; |
| |
| const bool is_self = attendee->FindBool(kAttendeesSelf).value_or(false); |
| if (!is_self) { |
| // A special case when user invited 1 more guest and removed themselves |
| // from the event. In this case that user will be returned as an |
| // attendee (the total number of attendees <= the requested number), safe |
| // to ignore this item. |
| continue; |
| } |
| |
| const auto* responseStatus = attendee->FindString(kAttendeesResponseStatus); |
| if (!responseStatus) |
| return absl::nullopt; |
| |
| const auto* it = kAttendeesResponseStatuses.find(*responseStatus); |
| if (it != kAttendeesResponseStatuses.end()) { |
| return it->second; |
| } |
| } |
| |
| return CalendarEvent::ResponseStatus::kUnknown; |
| } |
| |
| } // namespace |
| |
| DateTime::DateTime() = default; |
| |
| DateTime::DateTime(const DateTime& src) = default; |
| |
| DateTime& DateTime::operator=(const DateTime& src) = default; |
| |
| DateTime::~DateTime() = default; |
| |
| // static |
| void DateTime::RegisterJSONConverter( |
| base::JSONValueConverter<DateTime>* converter) { |
| converter->RegisterCustomField<base::Time>(kDateTime, &DateTime::date_time_, |
| &util::GetTimeFromString); |
| converter->RegisterCustomField<base::Time>(kDate, &DateTime::date_time_, |
| &util::GetDateOnlyFromString); |
| } |
| |
| // static |
| bool DateTime::CreateDateTimeFromValue(const base::Value* value, |
| DateTime* time) { |
| base::JSONValueConverter<DateTime> converter; |
| if (!converter.Convert(*value, time)) { |
| DVLOG(1) << "Unable to create: Invalid DateTime JSON!"; |
| return false; |
| } |
| return true; |
| } |
| |
| CalendarEvent::CalendarEvent() = default; |
| |
| CalendarEvent::~CalendarEvent() = default; |
| |
| CalendarEvent::CalendarEvent(const CalendarEvent&) = default; |
| |
| CalendarEvent& CalendarEvent::operator=(const CalendarEvent&) = default; |
| |
| // static |
| void CalendarEvent::RegisterJSONConverter( |
| base::JSONValueConverter<CalendarEvent>* converter) { |
| converter->RegisterStringField(kApiResponseIdKey, &CalendarEvent::id_); |
| converter->RegisterStringField(kSummary, &CalendarEvent::summary_); |
| converter->RegisterStringField(kHtmlLink, &CalendarEvent::html_link_); |
| converter->RegisterStringField(kColorId, &CalendarEvent::color_id_); |
| converter->RegisterCustomValueField(kStatus, &CalendarEvent::status_, |
| &ConvertEventStatus); |
| converter->RegisterCustomValueField(kStart, &CalendarEvent::start_time_, |
| &DateTime::CreateDateTimeFromValue); |
| converter->RegisterCustomValueField(kEnd, &CalendarEvent::end_time_, |
| &DateTime::CreateDateTimeFromValue); |
| } |
| |
| // static |
| std::unique_ptr<CalendarEvent> CalendarEvent::CreateFrom( |
| const base::Value& value) { |
| auto event = std::make_unique<CalendarEvent>(); |
| base::JSONValueConverter<CalendarEvent> converter; |
| if (!IsResourceKindExpected(value, kCalendarEventKind) || |
| !converter.Convert(value, event.get())) { |
| DVLOG(1) << "Unable to create: Invalid CalendarEvent JSON!"; |
| return nullptr; |
| } |
| |
| auto self_response_status = CalculateSelfResponseStatus(value); |
| if (self_response_status.has_value()) { |
| event->set_self_response_status(self_response_status.value()); |
| return event; |
| } |
| |
| DVLOG(1) << "Unable to calculate self response status: Invalid " |
| "CalendarEvent JSON!"; |
| return nullptr; |
| } |
| |
| int CalendarEvent::GetApproximateSizeInBytes() const { |
| int total_bytes = 0; |
| |
| total_bytes += sizeof(CalendarEvent); |
| total_bytes += id_.length(); |
| total_bytes += summary_.length(); |
| total_bytes += html_link_.length(); |
| total_bytes += color_id_.length(); |
| total_bytes += sizeof(status_); |
| total_bytes += sizeof(self_response_status_); |
| |
| return total_bytes; |
| } |
| |
| EventList::EventList() = default; |
| |
| EventList::~EventList() = default; |
| |
| // static |
| void EventList::RegisterJSONConverter( |
| base::JSONValueConverter<EventList>* converter) { |
| converter->RegisterStringField(kTimeZone, &EventList::time_zone_); |
| converter->RegisterStringField(kApiResponseETagKey, &EventList::etag_); |
| converter->RegisterStringField(kApiResponseKindKey, &EventList::kind_); |
| converter->RegisterCustomValueField(kApiResponseItemsKey, &EventList::items_, |
| &ConvertResponseItems); |
| } |
| |
| // static |
| std::unique_ptr<EventList> EventList::CreateFrom(const base::Value& value) { |
| auto events = std::make_unique<EventList>(); |
| base::JSONValueConverter<EventList> converter; |
| if (!IsResourceKindExpected(value, kCalendarEventListKind) || |
| !converter.Convert(value, events.get())) { |
| DVLOG(1) << "Unable to create: Invalid EventList JSON!"; |
| return nullptr; |
| } |
| return events; |
| } |
| |
| void EventList::InjectItemForTesting(std::unique_ptr<CalendarEvent> item) { |
| items_.push_back(std::move(item)); |
| } |
| |
| } // namespace calendar |
| } // namespace google_apis |