Czy powinieneś chronić się przed nieoczekiwanymi wartościami z zewnętrznych interfejsów API?


51

Powiedzmy, że kodujesz funkcję, która pobiera dane wejściowe z zewnętrznego interfejsu API MyAPI.

Ten zewnętrzny interfejs API MyAPIma umowę, która stwierdza, że ​​zwróci a stringlub a number.

Zaleca się, aby ustrzec się przed rzeczy takie jak null, undefined, boolean, itd., Mimo że nie jest częścią API MyAPI? W szczególności, ponieważ nie masz kontroli nad tym interfejsem API, nie możesz zagwarantować tego za pomocą analizy typu statycznego, więc lepiej być bezpiecznym niż żałować?

Mam na myśli zasadę solidności .


16
Jakie są skutki braku obsługi tych nieoczekiwanych wartości, jeśli zostaną zwrócone? Czy potrafisz żyć z tymi wpływami? Czy warto mieć złożoność obsługi tych nieoczekiwanych wartości, aby uniknąć konieczności radzenia sobie z wpływami?
Vincent Savard

55
Jeśli się ich spodziewasz, to z definicji nie są nieoczekiwane.
Mason Wheeler

28
Pamiętaj, że API nie jest zobowiązane do zwrócenia Ci tylko prawidłowego JSON (zakładam, że to JSON). Możesz także otrzymać odpowiedź typu<!doctype html><html><head><title>504 Gateway Timeout</title></head><body>The server was unable to process your request. Make sure you have typed the address correctly. If the problem persists, please try again later.</body></html>
użytkownik253751

5
Co oznacza „zewnętrzny interfejs API”? Czy nadal jest pod twoją kontrolą?
Deduplicator

11
„Dobry programista to ktoś, kto patrzy w obie strony przed przejściem przez ulicę jednokierunkową”.
jeroen_de_schutter

Odpowiedzi:


103

Nigdy nie należy ufać wejściom do oprogramowania, niezależnie od źródła. Ważne jest nie tylko sprawdzanie poprawności typów, ale także zakresów danych wejściowych i logiki biznesowej. W komentarzu jest to dobrze opisane przez OWASP

Niezastosowanie się do tego spowoduje w najlepszym wypadku śmieciowe dane, które musisz później wyczyścić, ale w najgorszym przypadku pozostawisz możliwość złośliwych exploitów, jeśli ta usługa zostanie w jakiś sposób naruszona (qv Target hack). Zakres problemów między nimi obejmuje doprowadzenie aplikacji do stanu niemożliwego do odzyskania.


Z komentarzy wynika, że ​​być może moja odpowiedź przydałaby się trochę rozbudowy.

Przez „nigdy nie ufaj wejściom” mam na myśli po prostu, że nie możesz zakładać, że zawsze będziesz otrzymywać ważne i godne zaufania informacje z wcześniejszych lub późniejszych systemów i dlatego zawsze powinieneś dezynfekować te dane wejściowe najlepiej jak potrafisz, lub odrzucić to.

Jeden komentarz pojawił się w komentarzach, które podam jako przykład. Chociaż tak, musisz w pewnym stopniu zaufać swojemu systemowi operacyjnemu, ale nie jest nieuzasadnione, na przykład, odrzucenie wyników generatora liczb losowych, jeśli poprosisz o numer od 1 do 10, a on odpowie „bob”.

Podobnie, w przypadku PO, zdecydowanie powinieneś upewnić się, że Twoja aplikacja akceptuje tylko prawidłowe dane wejściowe z usługi nadrzędnej. To, co zrobisz, gdy nie będzie w porządku, zależy od ciebie i zależy w dużej mierze od faktycznej funkcji biznesowej, którą próbujesz osiągnąć, ale minimalnie zalogowałbyś ją do późniejszego debugowania i w przeciwnym razie upewnisz się, że aplikacja nie pójdzie w stan niemożliwy do odzyskania lub niepewny.

Chociaż nigdy nie możesz poznać wszystkich możliwych danych wejściowych, które może dać ktoś / coś, z pewnością możesz ograniczyć to, co jest dozwolone, w zależności od wymagań biznesowych i wykonać jakąś formę białej listy danych wejściowych na tej podstawie.


20
Co oznacza skrót Qv?
JonH

15
@ JonH w zasadzie „patrz także” ... hack Target jest przykładem, że odwołuje się on do en.oxforddictionaries.com/definition/qv .
andrewtweber

8
Ta odpowiedź jest taka, jaka jest, po prostu nie ma sensu. Nie można przewidzieć wszystkich sposobów niewłaściwego zachowania się biblioteki innej firmy. Jeśli dokumentacja funkcji bibliotecznej wyraźnie zapewnia, że ​​wynik zawsze będzie miał pewne właściwości, powinieneś być w stanie polegać na tym, że projektanci zapewnili, że ta właściwość rzeczywiście się zachowa. Do ich obowiązków należy posiadanie zestawu testów, który sprawdza tego rodzaju rzeczy, i przesyłanie poprawki błędów w przypadku napotkania sytuacji, w której nie ma takiej możliwości. Ci sprawdzenie tych właściwości w swoim własnym kodem narusza zasadę DRY.
lewo około

23
@leftaroundabout nie, ale powinieneś być w stanie przewidzieć wszystkie ważne rzeczy, które Twoja aplikacja może zaakceptować i odrzucić resztę.
Paul

10
@leftaroundabout Nie chodzi o nieufność do wszystkiego, chodzi o nieufanie do zewnętrznych niezaufanych źródeł. Chodzi o modelowanie zagrożeń. Jeśli nie zrobiłeś tego, że twoje oprogramowanie nie jest bezpieczne (jak to może być, jeśli nigdy nie zastanawiałeś się nad tym, z jakimi aktorami i zagrożeniami chcesz zabezpieczyć swoją aplikację?). W przypadku oprogramowania biznesowego młyna rozsądnym jest zakładanie, że osoby dzwoniące mogą być złośliwe, a założenie, że Twój system operacyjny stanowi zagrożenie, rzadko jest uzasadnione.
Voo

33

Tak , oczywiście. Ale co sprawia, że ​​uważasz, że odpowiedź może być inna?

Na pewno nie chcesz, aby Twój program zachowywał się w nieprzewidziany sposób, na wypadek gdyby API nie zwróciło tego, co mówi umowa, prawda? Tak przynajmniej można mieć do czynienia z takim zachowaniem jakoś . Minimalna forma obsługi błędów jest zawsze warta (bardzo minimalnego!) Wysiłku i nie ma absolutnie żadnej wymówki, aby nie implementować czegoś takiego.

Jednak ile wysiłku powinieneś zainwestować, aby poradzić sobie z taką sprawą, zależy ona w dużej mierze od przypadku i można na nie odpowiedzieć tylko w kontekście twojego systemu. Często wystarczy krótki wpis w dzienniku i pełne zakończenie działania aplikacji. Czasami lepiej będzie wdrożyć szczegółową obsługę wyjątków, zajmując się różnymi formami „złych” wartości zwracanych, i być może trzeba będzie wdrożyć jakąś strategię awaryjną.

Ale robi to ogromną różnicę, jeśli piszesz tylko wewnętrzną aplikację do formatowania arkuszy kalkulacyjnych, z której może korzystać mniej niż 10 osób i gdzie wpływ finansowy awarii aplikacji jest niewielki, lub jeśli tworzysz nową autonomiczną jazdę samochodem system, w którym awaria aplikacji może kosztować życie.

Dlatego nie ma skrótu do zastanowienia się nad tym, co robisz , stosowanie zdrowego rozsądku jest zawsze obowiązkowe.


Co zrobić, to kolejna decyzja. Być może masz rozwiązanie awaryjne. Wszystko, co asynchroniczne, może zostać ponowione przed utworzeniem dziennika wyjątków (lub martwej litery). Aktywny alert dla dostawcy lub dostawcy może być opcją, jeśli problem będzie się powtarzał.
mckenzm

@mckenzm: fakt, że OP zadaje pytanie, gdzie dosłowna odpowiedź może oczywiście być tylko „tak”, jest IMHO znakiem, że mogą nie być zainteresowani dosłowną odpowiedzią. Wygląda na to, że pytają: „czy trzeba chronić się przed różnymi formami nieoczekiwanych wartości z interfejsu API i traktować je inaczej” ?
Doc Brown

1
hmm, podejście bzdura / karp / kostka. Czy to nasza wina za przekazywanie złych (ale legalnych) wniosków? czy odpowiedź jest możliwa, ale w szczególności nie jest dla nas użyteczna? czy odpowiedź jest uszkodzona? Różne scenariusze, teraz brzmi jak praca domowa.
mckenzm

21

Zasada niezawodności - w szczególności połowa jej „bądź liberalna w tym, co akceptujesz” - jest bardzo złym pomysłem w oprogramowaniu. Został pierwotnie opracowany w kontekście sprzętu, w którym ograniczenia fizyczne sprawiają, że tolerancje techniczne są bardzo ważne, ale w oprogramowaniu, gdy ktoś wysyła ci zniekształcone lub w inny sposób niewłaściwe dane wejściowe, masz dwie możliwości. Możesz to odrzucić (najlepiej z wyjaśnieniem, co poszło nie tak) lub spróbować dowiedzieć się, co to miało znaczyć.

EDYCJA: Okazuje się, że się myliłem w powyższym stwierdzeniu. Zasada niezawodności nie pochodzi ze świata sprzętu, ale z architektury internetowej, w szczególności RFC 1958 . W Stanach:

3.9 Bądź rygorystyczny podczas wysyłania i tolerancyjny podczas odbierania. Implementacje muszą być zgodne ze specyfikacjami dokładnie podczas wysyłania do sieci i tolerować błędne dane wejściowe z sieci. W razie wątpliwości należy po cichu odrzucić nieprawidłowe dane wejściowe, bez zwracania komunikatu o błędzie, chyba że specyfikacja tego wymaga.

Mówiąc wprost, jest to po prostu niewłaściwe od początku do końca. Trudno jest wyobrazić sobie bardziej błędne pojęcie obsługi błędów niż „dyskretne odrzucanie błędnych danych wejściowych bez zwracania komunikatu o błędzie” z powodów podanych w tym poście.

Zobacz także artykuł IETF „Szkodliwe konsekwencje zasady solidności” w celu dalszego rozwinięcia tej kwestii.

Nigdy, nigdy, nigdy nie wybieraj tej drugiej opcji, chyba że masz zasoby równoważne zespołowi wyszukiwania Google, aby rzucić na swój projekt, ponieważ to jest to, czego potrzeba, aby wymyślić program komputerowy, który wykonuje coś zbliżonego do przyzwoitej pracy w tej konkretnej domenie problemowej. (I nawet wtedy sugestie Google mają wrażenie, że mniej więcej w połowie wychodzą z lewego pola.) Jeśli spróbujesz to zrobić, skończy się to ogromnym bólem głowy, w którym Twój program będzie często próbował interpretować złe wejście jako X, gdy tak naprawdę nadawca miał na myśli Y.

Jest to złe z dwóch powodów. Oczywistym jest to, że wtedy masz złe dane w swoim systemie. Mniej oczywistym jest to, że w wielu przypadkach ani ty, ani nadawca nie zdacie sobie sprawy, że coś poszło nie tak, aż znacznie później w dół drogi, gdy coś wysadza ci w twarz, a potem nagle masz duży, drogi bałagan do naprawienia i nie masz pojęcia co poszło nie tak, ponieważ zauważalny efekt jest tak daleko od pierwotnej przyczyny.

Właśnie dlatego istnieje zasada Fail Fast; oszczędzaj wszystkim zaangażowanym ból głowy, stosując go do swoich interfejsów API.


7
Chociaż zgadzam się z zasadą tego, co mówisz, myślę, że mylisz się WRT z intencją zasady solidności. Nigdy nie widziałem, żeby miało to oznaczać „akceptuj złe dane”, a jedynie „nie bądź nadmiernie niespokojny w kwestii dobrych danych”. Na przykład, jeśli dane wejściowe to plik CSV, zasada niezawodności nie byłaby poprawnym argumentem przy analizie dat w nieoczekiwanym formacie, ale obsługiwałaby argument, że wnioskowanie o kolejności kolumn z wiersza nagłówka byłoby dobrym pomysłem .
Morgen

9
@Morgen: Zasada niezawodności została użyta, aby zasugerować, że przeglądarki powinny akceptować raczej niechlujny kod HTML, i doprowadziło do tego, że wdrożone witryny internetowe były znacznie niechlujniejsze niż byłyby, gdyby przeglądarki wymagały odpowiedniego kodu HTML. Dużą częścią tego problemu było jednak użycie wspólnego formatu dla treści generowanych przez ludzi i generowanych maszynowo, w przeciwieństwie do oddzielnych formatów edytowalnych przez człowieka i przetwarzanych maszynowo wraz z narzędziami do konwersji między nimi.
supercat

9
@supercat: mimo wszystko - lub po prostu stąd - HTML i WWW były niezwykle udane ;-)
Doc Brown

11
@DocBrown: Wiele naprawdę okropnych rzeczy stało się standardami po prostu dlatego, że były to pierwsze podejście, które stało się dostępne, gdy ktoś z dużą siłą przebicia musiał przyjąć coś, co spełnia pewne minimalne kryteria, a zanim zyskały przyczepność, było to za późno, aby wybrać coś lepszego.
supercat

5
@supercat Dokładnie. JavaScript od razu przychodzi na myśl, na przykład ...
Mason Wheeler

13

Zasadniczo kod powinien być konstruowany tak, aby zachowywał przynajmniej następujące ograniczenia, ilekroć jest to praktyczne:

  1. Po otrzymaniu poprawnego wejścia, wygeneruj prawidłowe wyjście.

  2. Gdy podano prawidłowe dane wejściowe (które mogą, ale nie muszą być poprawne), wygeneruj prawidłowe dane wyjściowe (podobnie).

  3. Jeśli podano nieprawidłowe dane wejściowe, należy je przetwarzać bez żadnych skutków ubocznych poza tymi powodowanymi przez normalne dane wejściowe lub te, które są zdefiniowane jako sygnalizowanie błędu.

W wielu sytuacjach programy będą zasadniczo przechodzić przez różne porcje danych, nie zwracając szczególnej uwagi na to, czy są poprawne. Jeśli takie fragmenty zawierają nieprawidłowe dane, wynik programu prawdopodobnie zawiera nieprawidłowe dane. O ile program nie został specjalnie zaprojektowany do sprawdzania poprawności wszystkich danych i nie zagwarantuje, że nie wygeneruje nieprawidłowych danych wyjściowych, nawet jeśli otrzyma nieprawidłowe dane wejściowe , programy przetwarzające dane wyjściowe powinny dopuszczać możliwość wystąpienia w nich nieprawidłowych danych.

Chociaż wczesna weryfikacja danych jest często pożądana, nie zawsze jest szczególnie praktyczna. Między innymi, jeśli ważność jednego fragmentu danych zależy od zawartości innych fragmentów i jeśli większość danych wprowadzonych do jakiejś sekwencji kroków zostanie odfiltrowana po drodze, ograniczając sprawdzanie poprawności do danych, które je przechodzą wszystkie etapy mogą dać znacznie lepszą wydajność niż próba sprawdzenia wszystkiego.

Ponadto, nawet jeśli jest tylko oczekuje się otrzymać wstępnie zatwierdzone dane program, często dobrze jest mieć go podtrzymywać powyższe ograniczenia i tak ilekroć praktyczne. Powtarzanie pełnej walidacji na każdym etapie przetwarzania często powoduje znaczne obniżenie wydajności, ale ograniczona ilość walidacji potrzebnej do utrzymania powyższych ograniczeń może być znacznie tańsza.


Następnie wszystko sprowadza się do podjęcia decyzji, czy wynikiem wywołania interfejsu API jest „wejście”.
mastov

@mastov: Odpowiedzi na wiele pytań będą zależeć od tego, jak zdefiniujemy „dane wejściowe” i „obserwowalne zachowania” / „wyniki”. Jeśli celem programu jest przetwarzanie liczb zapisanych w pliku, jego dane wejściowe można zdefiniować jako sekwencję liczb (w takim przypadku rzeczy, które nie są liczbami, nie są możliwymi danymi wejściowymi) lub jako plik (w którym to przypadku cokolwiek, co może pojawić się w pliku byłoby możliwe wejście).
supercat

3

Porównajmy dwa scenariusze i spróbuj dojść do wniosku.

Scenariusz 1 Nasza aplikacja zakłada, że ​​zewnętrzny interfejs API będzie działał zgodnie z umową.

Scenariusz 2 Nasza aplikacja zakłada, że ​​zewnętrzny interfejs API może źle działać, dlatego dodaje środki ostrożności.

Zasadniczo każdy interfejs API lub oprogramowanie może naruszyć umowy; może być spowodowane błędem lub nieoczekiwanymi warunkami. Nawet interfejs API może mieć problemy w wewnętrznych systemach, powodując nieoczekiwane wyniki.

Jeśli nasz program jest napisany przy założeniu, że zewnętrzny interfejs API będzie przestrzegać umów i uniknie dodawania jakichkolwiek środków ostrożności; kto będzie stroną w obliczu problemów? To my będziemy tymi, którzy napisali kod integracji.

Na przykład wybrane wartości zerowe. Powiedzmy, że zgodnie z umową API odpowiedź nie powinna mieć wartości zerowej; ale jeśli zostanie to nagle naruszone, nasz program spowoduje NPE.

Tak więc uważam, że lepiej będzie upewnić się, że aplikacja zawiera dodatkowy kod, aby rozwiązać nieoczekiwane scenariusze.


1

Zawsze powinieneś sprawdzać poprawność danych przychodzących - wprowadzonych przez użytkownika lub w inny sposób - dlatego powinieneś mieć proces do obsługi, gdy dane pobrane z tego zewnętrznego API są nieprawidłowe.

Ogólnie rzecz biorąc, każdy szew, w którym spotykają się systemy pozarządowe, powinien wymagać uwierzytelnienia, autoryzacji (jeśli nie jest zdefiniowany jedynie przez uwierzytelnienie) i walidacji.


1

Zasadniczo tak, zawsze należy chronić się przed błędnymi danymi wejściowymi, ale w zależności od rodzaju interfejsu API „ochrona” oznacza różne rzeczy.

W przypadku zewnętrznego interfejsu API dla serwera nie chcesz przypadkowo utworzyć polecenia, które powoduje awarię lub zagraża stanie serwera, więc musisz się przed tym zabezpieczyć.

W przypadku interfejsu API, takiego jak np. Klasa kontenera (lista, wektor itp.), Zgłaszanie wyjątków jest całkowicie dobrym wynikiem, kompromis w stanie instancji klasy może być do pewnego stopnia akceptowalny (np. Posortowany kontener wyposażony w wadliwy operator porównania nie będzie być posortowane), nawet zawieszenie aplikacji może być dopuszczalne, ale kompromis w stanie aplikacji - np. zapis w losowych lokalizacjach pamięci niezwiązanych z instancją klasy - najprawdopodobniej nie.


0

Aby wyrazić nieco odmienną opinię: myślę, że dopuszczalne jest po prostu praca z danymi, które otrzymałeś, nawet jeśli narusza to umowę. Zależy to od użycia: jest to coś, co MUSI być ciągiem dla Ciebie, czy jest to coś, co po prostu wyświetlasz / nie używasz itp. W tym drugim przypadku po prostu zaakceptuj to. Mam interfejs API, który potrzebuje tylko 1% danych dostarczonych przez inny interfejs. Nie obchodzi mnie, jakie dane są w 99%, więc nigdy tego nie sprawdzę.

Należy zachować równowagę między „błędami, ponieważ nie sprawdzam wystarczająco danych wejściowych” a „odrzucam prawidłowe dane, ponieważ jestem zbyt surowy”.


2
„Mam interfejs API, który potrzebuje tylko 1% danych dostarczonych przez inny interfejs”. To otwiera pytanie, dlaczego Twój interfejs API oczekuje 100 razy więcej danych, niż faktycznie potrzebuje. Jeśli musisz przechowywać nieprzejrzyste dane, aby je przekazać, tak naprawdę nie musisz określać, co to jest i nie musisz deklarować ich w żadnym konkretnym formacie, w którym to przypadku dzwoniący nie naruszy Twojej umowy .
Voo

1
@Voo - Podejrzewam, że dzwonią do zewnętrznego API (np. „Uzyskaj szczegółowe informacje o pogodzie dla miasta X”), a następnie wybierają dane, których potrzebują („aktualna temperatura”) i ignorują pozostałe zwrócone dane („opady deszczu”) ”,„ wiatr ”,„ temperatura prognozy ”,„ chłód wiatru ”itp.)
Stobor

@ChristianSauer - Myślę, że nie jesteś tak daleko od szerszego konsensusu - 1% danych, których używasz, ma sens sprawdzać, ale 99%, których niekoniecznie musisz sprawdzać. Musisz tylko sprawdzić rzeczy, które mogą spowodować wyzwolenie twojego kodu.
Stobor

0

Podejmuję to, by zawsze, zawsze sprawdzać każde wejście do mojego systemu. Oznacza to, że każdy parametr zwracany z interfejsu API powinien zostać sprawdzony, nawet jeśli mój program go nie używa. Mam tendencję do sprawdzania poprawności każdego parametru wysyłanego do interfejsu API. Istnieją tylko dwa wyjątki od tej reguły, patrz poniżej.

Powodem testowania jest to, że jeśli z jakiegoś powodu API / dane wejściowe są niepoprawne, mój program nie może na niczym polegać. Może mój program był powiązany ze starą wersją interfejsu API, która robi coś innego niż to, w co wierzę? Może mój program natrafił na błąd w programie zewnętrznym, który nigdy wcześniej nie miał miejsca. Albo jeszcze gorzej, zdarza się cały czas, ale nikogo to nie obchodzi! Być może haker oszukuje program zewnętrzny, aby zwracał rzeczy, które mogą zaszkodzić mojemu programowi lub systemowi?

Dwa wyjątki od testowania wszystkiego w moim świecie to:

  1. Wydajność po starannym pomiarze wydajności:

    • nigdy nie optymalizuj przed dokonaniem pomiaru. Testowanie wszystkich danych wejściowych / zwracanych najczęściej zajmuje bardzo mało czasu w porównaniu do rzeczywistego połączenia, więc usunięcie go często oszczędza niewiele lub nic. Nadal trzymałbym kod wykrycia błędu, ale skomentuj go, być może za pomocą makra lub po prostu komentując.
  2. Gdy nie masz pojęcia, co zrobić z błędem

    • zdarzają się chwile, nierzadko, kiedy twój projekt po prostu nie pozwala na naprawienie rodzaju błędu, który możesz znaleźć. Być może powinieneś zrobić, aby zarejestrować błąd, ale w systemie nie ma logowania błędów. Prawie zawsze można znaleźć sposób na „zapamiętanie” błędu, który pozwoli przynajmniej tobie jako programistowi sprawdzić go później. Liczniki błędów to jedna dobra rzecz w systemie, nawet jeśli zdecydujesz się nie rejestrować.

Dokładnie, jak dokładnie sprawdzić wartości wejściowe / zwracane, jest ważnym pytaniem. Na przykład, jeśli mówi się, że API zwraca ciąg znaków, sprawdziłbym, czy:

  • typ danych faktycznie jest ciągiem

  • i ta długość zawiera się między wartościami min. i maks. Zawsze sprawdzaj ciągi pod kątem maksymalnego rozmiaru, który mój program może obsłużyć (zwracanie zbyt dużych ciągów to klasyczny problem bezpieczeństwa w systemach sieciowych).

  • Niektóre ciągi powinny być sprawdzane pod kątem „nielegalnych” znaków lub treści, gdy jest to istotne. Jeśli Twój program może później wysłać łańcuch z informacją o bazie danych, dobrym pomysłem jest sprawdzenie ataków bazy danych (poszukiwanie iniekcji SQL). Te testy najlepiej wykonywać na granicach mojego systemu, gdzie mogę dokładnie określić, skąd pochodzi atak, i mogę wcześnie zawieść. Wykonanie pełnego testu wstrzykiwania SQL może być trudne, gdy łańcuchy zostaną później połączone, więc test należy wykonać przed wywołaniem bazy danych, ale jeśli jakieś problemy można wcześnie znaleźć, może być przydatne.

Powodem testowania parametrów wysyłanych do API jest upewnienie się, że otrzymam poprawny wynik. Ponownie wykonanie tych testów przed wywołaniem interfejsu API może wydawać się niepotrzebne, ale wymaga bardzo małej wydajności i może wykryć błędy w moim programie. Dlatego testy są najbardziej cenne przy opracowywaniu systemu (ale obecnie wydaje się, że każdy system jest w ciągłym rozwoju). W zależności od parametrów testy mogą być mniej lub bardziej dokładne, ale często stwierdzam, że często można ustawić dopuszczalne wartości minimalne i maksymalne dla większości parametrów, które mój program mógłby stworzyć. Być może ciąg powinien zawsze mieć co najmniej 2 znaki i mieć maksymalnie 2000 znaków? Minimalna i maksymalna wartość powinna mieścić się w tym, na co pozwala API, ponieważ wiem, że mój program nigdy nie użyje pełnego zakresu niektórych parametrów.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.