diff --git a/CshapStudy.DtoMapper/Common/Result.cs b/CshapStudy.DtoMapper/Common/Result.cs new file mode 100644 index 0000000..453cd52 --- /dev/null +++ b/CshapStudy.DtoMapper/Common/Result.cs @@ -0,0 +1,8 @@ +namespace CshapStudy.DtoMapper.Common; + +public abstract record Result +{ + public sealed record Success(TData data) : Result; + + public sealed record Error(TError error) : Result; +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/CshapStudy.DtoMapper.csproj b/CshapStudy.DtoMapper/CshapStudy.DtoMapper.csproj new file mode 100644 index 0000000..bbfa7b8 --- /dev/null +++ b/CshapStudy.DtoMapper/CshapStudy.DtoMapper.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/CshapStudy.DtoMapper/DTOs/PokemonDto.cs b/CshapStudy.DtoMapper/DTOs/PokemonDto.cs new file mode 100644 index 0000000..ac34e78 --- /dev/null +++ b/CshapStudy.DtoMapper/DTOs/PokemonDto.cs @@ -0,0 +1,286 @@ +using Newtonsoft.Json; + +namespace CshapStudy.DtoMapper.DTOs +{ + public class PokemonDto + { + [JsonProperty("abilities")] + public List? Abilities { get; set; } + + [JsonProperty("base_experience")] + public int? BaseExperience { get; set; } + + [JsonProperty("cries")] + public CriesDto? Cries { get; set; } + + [JsonProperty("forms")] + public List? Forms { get; set; } + + [JsonProperty("game_indices")] + public List? GameIndices { get; set; } + + [JsonProperty("height")] + public int? Height { get; set; } + + [JsonProperty("held_items")] + public List? HeldItems { get; set; } + + [JsonProperty("id")] + public int? Id { get; set; } + + [JsonProperty("is_default")] + public bool? IsDefault { get; set; } + + [JsonProperty("location_area_encounters")] + public string? LocationAreaEncounters { get; set; } + + [JsonProperty("moves")] + public List? Moves { get; set; } + + [JsonProperty("name")] + public string? Name { get; set; } + + [JsonProperty("order")] + public int? Order { get; set; } + + [JsonProperty("past_abilities")] + public List? PastAbilities { get; set; } + + [JsonProperty("past_types")] + public List? PastTypes { get; set; } + + [JsonProperty("species")] + public NamedApiResourceDto? Species { get; set; } + + [JsonProperty("sprites")] + public SpritesDto? Sprites { get; set; } + + [JsonProperty("stats")] + public List? Stats { get; set; } + + [JsonProperty("types")] + public List? Types { get; set; } + + [JsonProperty("weight")] + public int? Weight { get; set; } + } + + // --- 중첩된 DTO 클래스들 --- + + public class AbilitySlotDto + { + [JsonProperty("ability")] + public NamedApiResourceDto? Ability { get; set; } + + [JsonProperty("is_hidden")] + public bool? IsHidden { get; set; } + + [JsonProperty("slot")] + public int? Slot { get; set; } + } + + public class CriesDto + { + [JsonProperty("latest")] + public string? Latest { get; set; } + + [JsonProperty("legacy")] + public string? Legacy { get; set; } + } + + public class GameIndexDto + { + [JsonProperty("game_index")] + public int? GameIndex { get; set; } + + [JsonProperty("version")] + public NamedApiResourceDto? Version { get; set; } + } + + public class HeldItemDto + { + [JsonProperty("item")] + public NamedApiResourceDto? Item { get; set; } + + [JsonProperty("version_details")] + public List? VersionDetails { get; set; } + } + + public class VersionDetailDto + { + [JsonProperty("rarity")] + public int? Rarity { get; set; } + + [JsonProperty("version")] + public NamedApiResourceDto? Version { get; set; } + } + + public class MoveDto + { + [JsonProperty("move")] + public NamedApiResourceDto? Move { get; set; } + + [JsonProperty("version_group_details")] + public List? VersionGroupDetails { get; set; } + } + + public class MoveLearnMethodDto + { + [JsonProperty("level_learned_at")] + public int? LevelLearnedAt { get; set; } + + [JsonProperty("move_learn_method")] + public NamedApiResourceDto? MoveLearnMethod { get; set; } + + [JsonProperty("version_group")] + public NamedApiResourceDto? VersionGroup { get; set; } + } + + public class PastAbilityDto + { + [JsonProperty("abilities")] + public List? Abilities { get; set; } + + [JsonProperty("generation")] + public NamedApiResourceDto? Generation { get; set; } + } + + public class SpritesDto + { + [JsonProperty("back_default")] + public string? BackDefault { get; set; } + + [JsonProperty("back_female")] + public string? BackFemale { get; set; } // Nullable string + + [JsonProperty("back_shiny")] + public string? BackShiny { get; set; } + + [JsonProperty("back_shiny_female")] + public string? BackShinyFemale { get; set; } // Nullable string + + [JsonProperty("front_default")] + public string? FrontDefault { get; set; } + + [JsonProperty("front_female")] + public string? FrontFemale { get; set; } // Nullable string + + [JsonProperty("front_shiny")] + public string? FrontShiny { get; set; } + + [JsonProperty("front_shiny_female")] + public string? FrontShinyFemale { get; set; } // Nullable string + + [JsonProperty("other")] + public OtherSpritesDto? Other { get; set; } + + [JsonProperty("versions")] + public Dictionary? Versions { get; set; } + } + + public class OtherSpritesDto + { + [JsonProperty("dream_world")] + public DreamWorldDto? DreamWorld { get; set; } + + [JsonProperty("home")] + public HomeDto? Home { get; set; } + + [JsonProperty("official-artwork")] + public OfficialArtworkDto? OfficialArtwork { get; set; } + + [JsonProperty("showdown")] + public ShowdownDto? Showdown { get; set; } + } + + public class DreamWorldDto + { + [JsonProperty("front_default")] + public string? FrontDefault { get; set; } + + [JsonProperty("front_female")] + public string? FrontFemale { get; set; } + } + + public class HomeDto + { + [JsonProperty("front_default")] + public string? FrontDefault { get; set; } + + [JsonProperty("front_female")] + public string? FrontFemale { get; set; } + + [JsonProperty("front_shiny")] + public string? FrontShiny { get; set; } + + [JsonProperty("front_shiny_female")] + public string? FrontShinyFemale { get; set; } + } + + public class OfficialArtworkDto + { + [JsonProperty("front_default")] + public string? FrontDefault { get; set; } + + [JsonProperty("front_shiny")] + public string? FrontShiny { get; set; } + } + + public class ShowdownDto + { + [JsonProperty("back_default")] + public string? BackDefault { get; set; } + + [JsonProperty("back_female")] + public string? BackFemale { get; set; } + + [JsonProperty("back_shiny")] + public string? BackShiny { get; set; } + + [JsonProperty("back_shiny_female")] + public string? BackShinyFemale { get; set; } + + [JsonProperty("front_default")] + public string? FrontDefault { get; set; } + + [JsonProperty("front_female")] + public string? FrontFemale { get; set; } + + [JsonProperty("front_shiny")] + public string? FrontShiny { get; set; } + + [JsonProperty("front_shiny_female")] + public string? FrontShinyFemale { get; set; } + } + + public class StatDto + { + [JsonProperty("base_stat")] + public int? BaseStat { get; set; } + + [JsonProperty("effort")] + public int? Effort { get; set; } + + [JsonProperty("stat")] + public NamedApiResourceDto? Stat { get; set; } + } + + public class TypeDto + { + [JsonProperty("slot")] + public int? Slot { get; set; } + + [JsonProperty("type")] + public NamedApiResourceDto? Type { get; set; } + } + + // 이름과 URL을 가진 일반적인 리소스 객체 + public class NamedApiResourceDto + { + [JsonProperty("name")] + public string? Name { get; set; } + + [JsonProperty("url")] + public string? Url { get; set; } + } +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/DataSource/IPokemonApiDataSource.cs b/CshapStudy.DtoMapper/DataSource/IPokemonApiDataSource.cs new file mode 100644 index 0000000..73d05fc --- /dev/null +++ b/CshapStudy.DtoMapper/DataSource/IPokemonApiDataSource.cs @@ -0,0 +1,8 @@ +using CshapStudy.DtoMapper.DTOs; + +namespace CshapStudy.DtoMapper.DataSource; + +public interface IPokemonApiDataSource +{ + Task> GetPokemonAsync(string pokemonName); +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/DataSource/PokemonApiDataSource.cs b/CshapStudy.DtoMapper/DataSource/PokemonApiDataSource.cs new file mode 100644 index 0000000..9500d00 --- /dev/null +++ b/CshapStudy.DtoMapper/DataSource/PokemonApiDataSource.cs @@ -0,0 +1,34 @@ +using System.Reflection.Metadata.Ecma335; +using Newtonsoft.Json; + + +using CshapStudy.DtoMapper.DTOs; +namespace CshapStudy.DtoMapper.DataSource; + +public class PokemonApiDataSource : IPokemonApiDataSource +{ + private const string _BaseUrl = "https://pokeapi.co/api/v2/pokemon/"; + private HttpClient _httpClient; + + public PokemonApiDataSource(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task> GetPokemonAsync(string pokemonName) + { + var response = await _httpClient.GetAsync($"{_BaseUrl}/{pokemonName}"); + var jsonstring = await response.Content.ReadAsStringAsync(); + var headers = response.Headers.ToDictionary( + header => header.Key, + header => string.Join(",", header.Value) + ); + + return new Response( + statusCode: (int)response.StatusCode, + headers: headers, + body: JsonConvert.DeserializeObject(jsonstring)! + ); + } + +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/DataSource/Response.cs b/CshapStudy.DtoMapper/DataSource/Response.cs new file mode 100644 index 0000000..209c791 --- /dev/null +++ b/CshapStudy.DtoMapper/DataSource/Response.cs @@ -0,0 +1,18 @@ +using CshapStudy.DtoMapper.DTOs; + +namespace CshapStudy.DtoMapper.DataSource; + +public class Response +{ + public int StatusCode { get; set; } + public object Headers { get; set; } + public T Body { get; set; } + + public Response(int statusCode, object headers, T body) + { + StatusCode = statusCode; + Headers = headers; + Body = body; + + } +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/Mappers/PokemonMapper.cs b/CshapStudy.DtoMapper/Mappers/PokemonMapper.cs new file mode 100644 index 0000000..faddaa4 --- /dev/null +++ b/CshapStudy.DtoMapper/Mappers/PokemonMapper.cs @@ -0,0 +1,14 @@ +using CshapStudy.DtoMapper.DTOs; + +namespace CshapStudy.DtoMapper.Mappers; + +public static class PokemonMapper +{ + public static Models.Pokemon ToModel(this PokemonDto dto) + { + return new Models.Pokemon( + Name: dto.Name ?? "", + ImageUrl: dto.Sprites?.Other?.OfficialArtwork?.FrontDefault ?? "" + ); + } +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/Models/Pokemon.cs b/CshapStudy.DtoMapper/Models/Pokemon.cs new file mode 100644 index 0000000..d664231 --- /dev/null +++ b/CshapStudy.DtoMapper/Models/Pokemon.cs @@ -0,0 +1,39 @@ +namespace CshapStudy.DtoMapper.Models; + +public record Pokemon(String Name, String ImageUrl); +// public class Pokemon +// { +// public string Name { get; set; } +// public string Imageurl { get; set; } +// +// public Pokemon(string name, string imageUrl) +// { +// Name = name; +// Imageurl = imageUrl; +// +// } +// +// protected bool Equals(Pokemon other) +// { +// return Name == other.Name && Imageurl == other.Imageurl; +// } +// +// public override bool Equals(object? obj) +// { +// if(obj is null) return false; +// if (ReferenceEquals(this, obj)) return true; +// if (obj.GetType() != this.GetType()) return false; +// return Equals((Pokemon)obj); +// +// } +// +// public override int GetHashCode() +// { +// return HashCode.Combine(Name, Imageurl); +// } +// +// public override string ToString() +// { +// return $"{nameof(Name)}: {Name}, {nameof(Imageurl)}: {Imageurl}"; +// } +// } diff --git a/CshapStudy.DtoMapper/Program.cs b/CshapStudy.DtoMapper/Program.cs new file mode 100644 index 0000000..880844a --- /dev/null +++ b/CshapStudy.DtoMapper/Program.cs @@ -0,0 +1,9 @@ +namespace CshapStudy.DtoMapper; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/Repositories/IPokemonRepository.cs b/CshapStudy.DtoMapper/Repositories/IPokemonRepository.cs new file mode 100644 index 0000000..32363c0 --- /dev/null +++ b/CshapStudy.DtoMapper/Repositories/IPokemonRepository.cs @@ -0,0 +1,8 @@ +using CshapStudy.DtoMapper.Common; + +namespace CshapStudy.DtoMapper.Repositories; + +public interface IPokemonRepository +{ + Task> GetPokemonByNameAsync(string pokemonName); +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/Repositories/PoKemonError.cs b/CshapStudy.DtoMapper/Repositories/PoKemonError.cs new file mode 100644 index 0000000..ff264e5 --- /dev/null +++ b/CshapStudy.DtoMapper/Repositories/PoKemonError.cs @@ -0,0 +1,10 @@ +namespace CshapStudy.DtoMapper.Repositories; + +public enum PoKemonError +{ + NetWorkError, + NotFound, + InvalidInput, + UnknownError + +} \ No newline at end of file diff --git a/CshapStudy.DtoMapper/Repositories/PokemonRepository.cs b/CshapStudy.DtoMapper/Repositories/PokemonRepository.cs new file mode 100644 index 0000000..b6987f8 --- /dev/null +++ b/CshapStudy.DtoMapper/Repositories/PokemonRepository.cs @@ -0,0 +1,48 @@ +using CshapStudy.DtoMapper.Common; +using CshapStudy.DtoMapper.DataSource; +using CshapStudy.DtoMapper.Mappers; +using CshapStudy.DtoMapper.Models; + +namespace CshapStudy.DtoMapper.Repositories; + +public class PokemonRepository : IPokemonRepository +{ + private IPokemonApiDataSource _dataSource; + + public PokemonRepository(IPokemonApiDataSource dataSource) + { + _dataSource = dataSource; + } + + public async Task> GetPokemonByNameAsync(string pokemonName) + { + if (String.IsNullOrWhiteSpace(pokemonName)) + { + return new Result.Error(PoKemonError.InvalidInput); + } + try + { + var response = await _dataSource.GetPokemonAsync(pokemonName); + + switch (response.StatusCode) + { + case 200: + var dto = response.Body; + Pokemon pokemon = dto.ToModel(); + return new Result.Success(pokemon); + case 404: + return new Result.Error(PoKemonError.NotFound); + default: + return new Result.Error(PoKemonError.UnknownError); + } + } + catch (Exception e) + { + + return new Result.Error(PoKemonError.UnknownError); + + + + } + } +} \ No newline at end of file diff --git a/CshapStudy.realtimeStationArrival/Common/Response.cs b/CshapStudy.realtimeStationArrival/Common/Response.cs new file mode 100644 index 0000000..10dfbe3 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Common/Response.cs @@ -0,0 +1,15 @@ +namespace CshapStudy.realtimeStationArrival.Common; + +public class Response +{ + public int StatusCode { get; set; } + public IReadOnlyDictionary Headers { get; set; } + public T Body { get; set; } + + public Response(int statusCode, IReadOnlyDictionary headers, T body) + { + StatusCode = statusCode; + Headers = headers; + Body = body; + } +} \ No newline at end of file diff --git a/CshapStudy.realtimeStationArrival/Common/Result.cs b/CshapStudy.realtimeStationArrival/Common/Result.cs new file mode 100644 index 0000000..876f300 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Common/Result.cs @@ -0,0 +1,8 @@ +namespace CshapStudy.realtimeStationArrival.Common; + +public abstract record Result +{ + public sealed record Success(TData data) : Result; + + public sealed record Error(TError error) : Result; +} \ No newline at end of file diff --git a/CshapStudy.realtimeStationArrival/CshapStudy.realtimeStationArrival.csproj b/CshapStudy.realtimeStationArrival/CshapStudy.realtimeStationArrival.csproj new file mode 100644 index 0000000..b404ad5 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/CshapStudy.realtimeStationArrival.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + ..\..\..\..\..\..\Applications\Rider.app\Contents\lib\ReSharperHost\TestRunner\netcoreapp3.0\JetBrains.ReSharper.TestRunner.Merged.dll + + + + diff --git a/CshapStudy.realtimeStationArrival/DTOs/StationDto.cs b/CshapStudy.realtimeStationArrival/DTOs/StationDto.cs new file mode 100644 index 0000000..2093d09 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/DTOs/StationDto.cs @@ -0,0 +1,132 @@ +using System.Text.Json.Serialization; + +namespace CshapStudy.realtimeStationArrival.DTOs; +using Newtonsoft.Json; +using System.Collections.Generic; + +public class StationDto +{ + [JsonPropertyName("errorMessage")] + public ErrorMessage ErrorMessage { get; set; } + + [JsonPropertyName("realtimeArrivalList")] + public List RealtimeArrivalList { get; set; } + +} + +public class ErrorMessage +{ + [JsonPropertyName("status")] + public int Status { get; set; } + + [JsonPropertyName("code")] + public string Code { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } + + [JsonPropertyName("link")] + public string Link { get; set; } + + [JsonPropertyName("developerMessage")] + public string DeveloperMessage { get; set; } + + [JsonPropertyName("total")] + public int Total { get; set; } +} + +public class RealtimeArrival +{ + [JsonPropertyName("beginRow")] + public object BeginRow { get; set; } + + [JsonPropertyName("endRow")] + public object EndRow { get; set; } + + [JsonPropertyName("curPage")] + public object CurPage { get; set; } + + [JsonPropertyName("pageRow")] + public object PageRow { get; set; } + + [JsonPropertyName("totalCount")] + public int TotalCount { get; set; } + + [JsonPropertyName("rowNum")] + public int RowNum { get; set; } + + [JsonPropertyName("selectedCount")] + public int SelectedCount { get; set; } + + [JsonPropertyName("subwayId")] + public string SubwayId { get; set; } + + [JsonPropertyName("subwayNm")] + public object SubwayNm { get; set; } + + [JsonPropertyName("updnLine")] + public string UpdnLine { get; set; } + + [JsonPropertyName("trainLineNm")] + public string TrainLineNm { get; set; } + + [JsonPropertyName("subwayHeading")] + public object SubwayHeading { get; set; } + + [JsonPropertyName("statnFid")] + public string StatnFid { get; set; } + + [JsonPropertyName("statnTid")] + public string StatnTid { get; set; } + + [JsonPropertyName("statnId")] + public string StatnId { get; set; } + + [JsonPropertyName("statnNm")] + public string StatnNm { get; set; } + + [JsonPropertyName("trainCo")] + public object TrainCo { get; set; } + + [JsonPropertyName("trnsitCo")] + public string TrnsitCo { get; set; } + + [JsonPropertyName("ordkey")] + public string Ordkey { get; set; } + + [JsonPropertyName("subwayList")] + public string SubwayList { get; set; } + + [JsonPropertyName("statnList")] + public string StatnList { get; set; } + + [JsonPropertyName("btrainSttus")] + public string BtrainSttus { get; set; } + + [JsonPropertyName("barvlDt")] + public string BarvlDt { get; set; } + + [JsonPropertyName("btrainNo")] + public string BtrainNo { get; set; } + + [JsonPropertyName("bstatnId")] + public string BstatnId { get; set; } + + [JsonPropertyName("bstatnNm")] + public string BstatnNm { get; set; } + + [JsonPropertyName("recptnDt")] + public string RecptnDt { get; set; } + + [JsonPropertyName("arvlMsg2")] + public string ArvlMsg2 { get; set; } + + [JsonPropertyName("arvlMsg3")] + public string ArvlMsg3 { get; set; } + + [JsonPropertyName("arvlCd")] + public string ArvlCd { get; set; } + + [JsonPropertyName("lstcarAt")] + public string LstcarAt { get; set; } +} \ No newline at end of file diff --git a/CshapStudy.realtimeStationArrival/DataSources/IStationApiDataSource.cs b/CshapStudy.realtimeStationArrival/DataSources/IStationApiDataSource.cs new file mode 100644 index 0000000..41f0292 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/DataSources/IStationApiDataSource.cs @@ -0,0 +1,9 @@ +using CshapStudy.realtimeStationArrival.Common; +using CshapStudy.realtimeStationArrival.DTOs; + +namespace CshapStudy.realtimeStationArrival.DataSources; + +public interface IStationApiDataSource +{ + Task> GetStationAsync(string stationName); +} \ No newline at end of file diff --git a/CshapStudy.realtimeStationArrival/DataSources/StationApiDataSource.cs b/CshapStudy.realtimeStationArrival/DataSources/StationApiDataSource.cs new file mode 100644 index 0000000..7dca5ab --- /dev/null +++ b/CshapStudy.realtimeStationArrival/DataSources/StationApiDataSource.cs @@ -0,0 +1,35 @@ +using CshapStudy.realtimeStationArrival.Common; +using CshapStudy.realtimeStationArrival.DTOs; +using CshapStudy.realtimeStationArrival.Models; +using Newtonsoft.Json; + +namespace CshapStudy.realtimeStationArrival.DataSources; + +public class StationApiDataSource : IStationApiDataSource +{ + private const string BaseUrl ="http://swopenapi.seoul.go.kr/api/subway/sample/json/realtimeStationArrival/0/5/"; + private HttpClient _httpClient; + + public StationApiDataSource(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task> GetStationAsync(string stationName) + { + var encodedStationName = System.Net.WebUtility.UrlEncode(stationName); // 이 줄을 추가합니다. + var response = await _httpClient.GetAsync($"{BaseUrl}{encodedStationName}"); // encodedStationName을 사용합니다. + var jsonString = await response.Content.ReadAsStringAsync(); + var headers = response.Headers.ToDictionary( + header => header.Key, + header => string.Join(",", header.Value) + ); + + return new Response( + statusCode: (int)response.StatusCode, + headers: headers, + body: JsonConvert.DeserializeObject(jsonString)! + ); + } +} + diff --git a/CshapStudy.realtimeStationArrival/Get.http b/CshapStudy.realtimeStationArrival/Get.http new file mode 100644 index 0000000..a3d7813 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Get.http @@ -0,0 +1,4 @@ +### +GET http://swopenapi.seoul.go.kr/api/subway/sample/json/realtimeStationArrival/0/5/%EA%B0%95%EB%82%A8 + + diff --git a/CshapStudy.realtimeStationArrival/Mappers/StationMapper.cs b/CshapStudy.realtimeStationArrival/Mappers/StationMapper.cs new file mode 100644 index 0000000..63fe4a7 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Mappers/StationMapper.cs @@ -0,0 +1,25 @@ +using CshapStudy.realtimeStationArrival.DTOs; +using CshapStudy.realtimeStationArrival.Models; +using System.Collections.Generic; +using System.Linq; + +namespace CshapStudy.realtimeStationArrival.Mappers; + +public static class StationMapper +{ + public static List ToModels(this StationDto dto) + { + + if (dto?.RealtimeArrivalList == null) + { + return new List(); + } + + return dto.RealtimeArrivalList.Select(arrival => new Station( + trainLineName: arrival.TrainLineNm ?? "정보 없음", + destinationStation: arrival.BstatnNm ?? "정보 없음", + currentLocationMessage: arrival.ArvlMsg3 ?? "정보 없음", + subwayLineId: arrival.SubwayId ?? "정보 없음" + )).ToList(); + } +} \ No newline at end of file diff --git a/CshapStudy.realtimeStationArrival/Models/Station.cs b/CshapStudy.realtimeStationArrival/Models/Station.cs new file mode 100644 index 0000000..679d486 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Models/Station.cs @@ -0,0 +1,23 @@ +using CshapStudy.realtimeStationArrival.DTOs; + +namespace CshapStudy.realtimeStationArrival.Models; + +public class Station : StationDto +{ + public string TrainLineName { get; } + public string DestinationStation { get; } + public string CurrentLocationMessage { get; } + public string SubwayLineId { get; } + + public Station(string trainLineName, string destinationStation, string currentLocationMessage, string subwayLineId) + { + TrainLineName = trainLineName; + DestinationStation = destinationStation; + CurrentLocationMessage = currentLocationMessage; + SubwayLineId = subwayLineId; + + } + public string SubwayId { get; set; } + public string TrainLineNm { get; set; } + public string ArvlMsg2 { get; set; } +} \ No newline at end of file diff --git a/CshapStudy.realtimeStationArrival/Program.cs b/CshapStudy.realtimeStationArrival/Program.cs new file mode 100644 index 0000000..5ae9dc0 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Program.cs @@ -0,0 +1,45 @@ +using CshapStudy.realtimeStationArrival.Common; +using CshapStudy.realtimeStationArrival.DataSources; +using CshapStudy.realtimeStationArrival.Models; +using CshapStudy.realtimeStationArrival.Repositories; + +namespace CshapStudy.realtimeStationArrival; + +class Program +{ + static async Task Main(string[] args) + { + IStationApiDataSource dataSource = new StationApiDataSource(new HttpClient()); + IStationRepository repository = new StationRepository(dataSource); + + var stationName = "온수"; + + Console.WriteLine($"'{stationName}'역 실시간 도착 정보 가져오는 중..."); + + var result = await repository.GetStationAsync(stationName); + + // Check if the result is a 'Success' record and extract its data + if (result is Result, StationError>.Success successResult) + { + var arrivals = successResult.data; + + if (!arrivals.Any()) + { + Console.WriteLine("정보를 찾을 수 없거나 오류가 발생했습니다."); + } + else + { + Console.WriteLine("--- 도착 정보 ---"); + + foreach (var arrival in arrivals) + { + Console.WriteLine($"[호선: {arrival.CurrentLocationMessage}] [방면: {arrival.TrainLineName}] -> {arrival.ArvlMsg2}"); + } + } + } + else if (result is Result, StationError>.Error errorResult) + { + Console.WriteLine($"오류 발생: {errorResult.error}"); // Access the error property of the Error record + } + } +} diff --git a/CshapStudy.realtimeStationArrival/Repositories/IStationRepository.cs b/CshapStudy.realtimeStationArrival/Repositories/IStationRepository.cs new file mode 100644 index 0000000..fc19a06 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Repositories/IStationRepository.cs @@ -0,0 +1,10 @@ +using CshapStudy.realtimeStationArrival.Common; +using CshapStudy.realtimeStationArrival.Models; + +namespace CshapStudy.realtimeStationArrival.Repositories; + +public interface IStationRepository +{ + Task,StationError>> GetStationAsync(String stationName); + +} \ No newline at end of file diff --git a/CshapStudy.realtimeStationArrival/Repositories/StationError.cs b/CshapStudy.realtimeStationArrival/Repositories/StationError.cs new file mode 100644 index 0000000..d092f6a --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Repositories/StationError.cs @@ -0,0 +1,10 @@ +namespace CshapStudy.realtimeStationArrival.Repositories; + +public enum StationError +{ + NetworkError, + NotFound, + InvalidInput, + UnknownError +} + diff --git a/CshapStudy.realtimeStationArrival/Repositories/StationRepository.cs b/CshapStudy.realtimeStationArrival/Repositories/StationRepository.cs new file mode 100644 index 0000000..8a6ff52 --- /dev/null +++ b/CshapStudy.realtimeStationArrival/Repositories/StationRepository.cs @@ -0,0 +1,48 @@ +using CshapStudy.realtimeStationArrival.DataSources; +using CshapStudy.realtimeStationArrival.Mappers; +using CshapStudy.realtimeStationArrival.Models; +using CshapStudy.realtimeStationArrival.Common; + +namespace CshapStudy.realtimeStationArrival.Repositories; + +public class StationRepository : IStationRepository +{ + private IStationApiDataSource _dataSource; + + public StationRepository(IStationApiDataSource dataSource) + { + _dataSource = dataSource; + } + + public async Task, StationError>> GetStationAsync(string stationName) + { + if (string.IsNullOrWhiteSpace(stationName)) + { + return new Result, StationError>.Error(StationError.InvalidInput); + } + + try + { + var response = await _dataSource.GetStationAsync(stationName); + + switch (response.StatusCode) + { + case 200: + var dto = response.Body; + var station = dto.ToModels(); + return new Result, StationError>.Success(station); + case 404: + return new Result, StationError>.Error(StationError.NotFound); + default: + return new Result, StationError>.Error(StationError.UnknownError); + } + + } + catch (Exception) + { + return new Result, StationError>.Error(StationError.NetworkError); + } + } +} + + diff --git a/CsharpStudy.2hakgi/TIL/2025_0915 b/CsharpStudy.2hakgi/TIL/2025_0915 new file mode 100644 index 0000000..ac18768 --- /dev/null +++ b/CsharpStudy.2hakgi/TIL/2025_0915 @@ -0,0 +1,19 @@ +2025_0915 + +문자열도 받고 숫자도 받고 싶다면 object로 받으면 됨 + +DTO는 JSON을 그대로 담는 객체 변환하는게 mapper (방어수단) +모든 필드가 Nullable인 것은 안터지려고 + +!은 null이 아님을 보증한다. 실제로 쓸일이 없지만 +Mapper에서 쓴다. 터지면 Mapper에서만 터질수 있도록 +데이터 잘못되면 Mapper만 수정하면되니까. + +DTO 필요한 이유 Model Class 는 null 이 없도록 +불완전한 코드를 포함될것 같으면 DTO를 포함하자. +안넣어도 될떄는 Mapper도 필요없다. + + +DTO가 도입되면, +직렬화, 역직렬화 데이터소스에서 하게 되고 모델클래스에는 도메인객체가 된다. + diff --git a/CsharpStudy.2hakgi/TIL/2025_0916 b/CsharpStudy.2hakgi/TIL/2025_0916 new file mode 100644 index 0000000..59ae0de --- /dev/null +++ b/CsharpStudy.2hakgi/TIL/2025_0916 @@ -0,0 +1,17 @@ +2025_0916 +Result +성공과 실패 두 종류가 있는데 + +실패는 타임아웃과 연결실패 논리적문제 등등 + +에러처리의 기본은 try-cath +어떤값이 빠져있거나 값이 들어있는데 말이 안되는 값이 들어왔다는건 안된다. +rutime에 터지는것만 해준다. + +Sealed 는 상속을 지정된것만 한다. +서브타입을 봉인한다. + + +실패의 리턴은 2개이상으로 내가 만든 실패로 리턴된다. + +길게 쓸꺼를 짧게 쓰는것을 레코드 g \ No newline at end of file diff --git a/CsharpStudy.DtoMapper.Tests/CsharpStudy.DtoMapper.Tests.csproj b/CsharpStudy.DtoMapper.Tests/CsharpStudy.DtoMapper.Tests.csproj new file mode 100644 index 0000000..0c3a9f4 --- /dev/null +++ b/CsharpStudy.DtoMapper.Tests/CsharpStudy.DtoMapper.Tests.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + + false + + CsharpStudy.DtoTest + + + + + + + + + + + + + \ No newline at end of file diff --git a/CsharpStudy.DtoMapper.Tests/Mappers/PokemonMapperTest.cs b/CsharpStudy.DtoMapper.Tests/Mappers/PokemonMapperTest.cs new file mode 100644 index 0000000..d5da9fe --- /dev/null +++ b/CsharpStudy.DtoMapper.Tests/Mappers/PokemonMapperTest.cs @@ -0,0 +1,46 @@ +using System.Net.Http; +using System.Collections.Generic; +using System.Runtime; +using System.Threading.Tasks; +using CshapStudy.DtoMapper.DataSource; +using CshapStudy.DtoMapper.Mappers; +using CshapStudy.DtoMapper.Repositories; +using NUnit.Framework; +using CshapStudy.DtoMapper.DTOs; +using CshapStudy.DtoMapper.Models; + +namespace CsharpStudy.DtoTest.Mappers; + +[TestFixture] +[TestOf(typeof(PokemonMapper))] +public class PokemonMapperTest +{ + [Test] + + public async Task GET_Diito() + { + IPokemonApiDataSource dataSource = new PokemonApiDataSource(new HttpClient()); + Response response = await dataSource.GetPokemonAsync("ditto"); + PokemonDto pokemonDto = response.Body; + Pokemon pokemon = pokemonDto.ToModel(); + Assert.That(pokemon.Name, Is.EqualTo("ditto")); + + } + [Test] + public async Task GET_Ditto_Image_Url() + { + + IPokemonApiDataSource dataSource = new PokemonApiDataSource(new HttpClient()); + + + Response response = await dataSource.GetPokemonAsync("ditto"); + PokemonDto pokemonDto = response.Body; + Pokemon pokemon = pokemonDto.ToModel(); + + + Assert.That(pokemon.Name, Is.EqualTo("ditto")); + + } + + +} \ No newline at end of file diff --git a/CsharpStudy.DtoMapper.Tests/Repositories/PokemonRepositoryTest.cs b/CsharpStudy.DtoMapper.Tests/Repositories/PokemonRepositoryTest.cs new file mode 100644 index 0000000..5137cbe --- /dev/null +++ b/CsharpStudy.DtoMapper.Tests/Repositories/PokemonRepositoryTest.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using CshapStudy.DtoMapper.Common; +using CshapStudy.DtoMapper.DataSource; +using CshapStudy.DtoMapper.DTOs; +using CshapStudy.DtoMapper.Models; +using CshapStudy.DtoMapper.Repositories; +using NUnit.Framework; + +namespace CsharpStudy.DtoTest.Repositories; + +[TestFixture] +[TestOf(typeof(PokemonRepository))] +public class PokemonRepositoryTest +{ + [Test] + public async Task Result_Test() + { + IPokemonRepository repository = new PokemonRepository(new PokemonApiDataSource(new HttpClient())); + + Result result = await repository.GetPokemonByNameAsync("ditto"); + + Assert.That(result is Result.Success, Is.True); + + Pokemon pokemon = (result as Result.Success)!.data; + Assert.That(pokemon.Name, Is.EqualTo("ditto")); + } + + [Test] + public async Task Result_Fail_Test() + { + IPokemonRepository repository = new PokemonRepository(new MockTimeoutDataSource()); + Result result = await repository.GetPokemonByNameAsync("dittoo"); + Assert.That(result is Result.Error, Is.False); + + result = await repository.GetPokemonByNameAsync("timeout"); + var errorResult = result as Result.Error; + Assert.That(errorResult!.error == PoKemonError.UnknownError, Is.True); + + await repository.GetPokemonByNameAsync("serialization-error"); + Assert.That(errorResult!.error == PoKemonError.UnknownError, Is.True); + + + + + } + + +} + +public class MockTimeoutDataSource : IPokemonApiDataSource +{ + public async Task> GetPokemonAsync(string pokemonName) + { + if (pokemonName == "timeout") + { + //항상 타임아웃(TimeoutException)발생하는 MockDataSource작성 + throw new TimeoutException("항상 타임아웃(TimeoutException)발생하는 MockDataSource작성"); + } + else if (pokemonName == "serialization-error") + { + //항상 타임아웃(JsonSerilzationException)발생하는 MocDataSource작성 + throw new JsonException("항상 타임아웃(JsonSerilzationException)발생하는 MocDataSource작성"); + } + + // 필요시 에러타입 추가 + return new Response(200, new { }, new PokemonDto { Name = "ditt" }); + } +} \ No newline at end of file diff --git a/CsharpStudy.DtoMapper/CsharpStudy.DtoMapper.csproj b/CsharpStudy.DtoMapper/CsharpStudy.DtoMapper.csproj new file mode 100644 index 0000000..fb5d5a7 --- /dev/null +++ b/CsharpStudy.DtoMapper/CsharpStudy.DtoMapper.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/CsharpStudy.DtoMapper/Mappers/PokemonMapperTest.cs b/CsharpStudy.DtoMapper/Mappers/PokemonMapperTest.cs new file mode 100644 index 0000000..5571194 --- /dev/null +++ b/CsharpStudy.DtoMapper/Mappers/PokemonMapperTest.cs @@ -0,0 +1,17 @@ +using CshapStudy.DtoMapper.Mappers; +using JetBrains.Annotations; +using NUnit.Framework; + +namespace CsharpStudy.DtoMapper.Mappers; + +[TestFixture] +[TestSubject(typeof(PokemonMapper))] +public class PokemonMapperTest +{ + + [Test] + public void METHOD() + { + + } +} \ No newline at end of file diff --git a/CsharpStudy.sln b/CsharpStudy.sln index d6370eb..04a3b3d 100644 --- a/CsharpStudy.sln +++ b/CsharpStudy.sln @@ -30,6 +30,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsharpStudy.Http", "CsharpS EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsharpStudy.Http.Tests", "CsharpStudy.Http.Tests\CsharpStudy.Http.Tests.csproj", "{E0A1FA34-6A03-4FBE-A23E-CF3280E5EAA6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CshapStudy.DtoMapper", "CshapStudy.DtoMapper\CshapStudy.DtoMapper.csproj", "{DFAB44CD-E506-4223-A0F0-DE1C368E67F5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsharpStudy.DtoMapper.Tests", "CsharpStudy.DtoMapper.Tests\CsharpStudy.DtoMapper.Tests.csproj", "{FB6E5337-6862-4D41-925E-349E02457B83}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CshapStudy.realtimeStationArrival", "CshapStudy.realtimeStationArrival\CshapStudy.realtimeStationArrival.csproj", "{D0970D7A-E9F1-46B3-A809-D5A87A0671FE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -96,5 +102,17 @@ Global {E0A1FA34-6A03-4FBE-A23E-CF3280E5EAA6}.Debug|Any CPU.Build.0 = Debug|Any CPU {E0A1FA34-6A03-4FBE-A23E-CF3280E5EAA6}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0A1FA34-6A03-4FBE-A23E-CF3280E5EAA6}.Release|Any CPU.Build.0 = Release|Any CPU + {DFAB44CD-E506-4223-A0F0-DE1C368E67F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFAB44CD-E506-4223-A0F0-DE1C368E67F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFAB44CD-E506-4223-A0F0-DE1C368E67F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFAB44CD-E506-4223-A0F0-DE1C368E67F5}.Release|Any CPU.Build.0 = Release|Any CPU + {FB6E5337-6862-4D41-925E-349E02457B83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB6E5337-6862-4D41-925E-349E02457B83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB6E5337-6862-4D41-925E-349E02457B83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB6E5337-6862-4D41-925E-349E02457B83}.Release|Any CPU.Build.0 = Release|Any CPU + {D0970D7A-E9F1-46B3-A809-D5A87A0671FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0970D7A-E9F1-46B3-A809-D5A87A0671FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0970D7A-E9F1-46B3-A809-D5A87A0671FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0970D7A-E9F1-46B3-A809-D5A87A0671FE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal